1console.log("Init register..."); 2class PageState { 3 constructor() { 4 this.tabs = ["company-info", "personal-info", "documents"]; 5 this.activeTab = "company-info"; 6 this.companyInfoFields = { 7 "companyName": { 8 "name": "Company name", 9 "value": null, 10 "error": null 11 }, 12 "tinNo": { 13 "name": "TIN number", 14 "value": null, 15 "error": null 16 }, 17 "address": { 18 "name": "Address", 19 "value": null, 20 "error": null 21 }, 22 "businessType": { 23 "name": "Business specialization", 24 "value": null, 25 "error": null 26 }, 27 "no": { 28 "name": "Estimated volume", 29 "value": null, 30 "error": null 31 }, 32 "no2": { 33 "name": "Estimated volume", 34 "value": null, 35 "error": null 36 } 37 }; 38 this.personalInfoFields = { 39 "firstName": { 40 "name": "First name", 41 "value": null, 42 "error": null 43 }, 44 "lastName": { 45 "name": "Last name", 46 "value": null, 47 "error": null 48 }, 49 "position": { 50 "name": "Position", 51 "value": null, 52 "error": null 53 }, 54 "email": { 55 "name": "Email", 56 "value": null, 57 "error": null 58 }, 59 // "mobile": { 60 // "name": "Phone number", 61 // "value": null, 62 // "error": null 63 // }, 64 // "securityQuestions": { 65 // "name": "Security questions", 66 // "value": null, 67 // "error": null 68 // }, 69 }; 70 this.documents = { 71 "crt": { 72 "value": null, 73 "error": null 74 }, 75 "memo": { 76 "value": null, 77 "error": null 78 }, 79 "tax": { 80 "value": null, 81 "error": null 82 }, 83 "tin": { 84 "value": null, 85 "error": null 86 }, 87 "businessLicense": { 88 "value": null, 89 "error": null 90 }, 91 "insuranceNote": { 92 "value": null, 93 "error": null 94 }, 95 "moa": { 96 "value": null, 97 "error": null 98 }, 99 "aoa": { 100 "value": null, 101 "error": null 102 }, 103 "acquisitionChannel": { 104 "value": null, 105 "error": null 106 }, 107 "terms": { 108 "value": null, 109 "error": null 110 } 111 } 112 this.securityQuestions = { 113 1: "What is your favorite color?", 114 3: "What is your pet's name?", 115 4: "What was the name of your math teacher?", 116 5: "What was your childhood nickname?", 117 6: "What is the name of your favorite childhood friend?", 118 }; 119 this.selectedSecurityQuestions = {}; 120 this.maxFileSizeInMB = 5; 121 this.totalMaxFileSizeInMB = 15; 122 this.mergedDocument = null; 123 } 124 125 setActiveTab(tabId) { 126 if (!this.tabs.includes(tabId)) {
127 console.warn("tabId is invalid"); 128 return; 129 } 130 131 this.activeTab = tabId; 132 this.tabs.forEach(tabId => { 133 $(`#${tabId}`).removeClass("active"); 134 }) 135 $(`#${this.activeTab}`).addClass("active"); 136 $(`.nav-link`).removeClass("active"); 137 $(`#${this.activeTab}-tab-btn`).addClass("active"); 138 } 139 140 setSecurityQuestions(data) { 141 this.selectedSecurityQuestions = data; 142 this.renderSecurityQuestions(); 143 } 144 145 renderSecurityQuestions() { 146 let htmlString = ""; 147 $(".security-questions-table tbody").html(htmlString); 148 for (const [questionId, questionObj] of Object.entries( 149 this.selectedSecurityQuestions 150 )) { 151 htmlString += ` 152 <tr data-row-id="${questionId}"> 153 <td>${questionObj.question}</td> 154 <td class='q-answer'>****</td> 155 <td> 156 <button class="btn btn-sm bg-light text-dark me-2 view-btn show" data-question-id="${questionId}">show</button> 157 <button class="btn btn-sm bg-light text-dark remove-btn" data-question-id="${questionId}">remove</button> 158 </td> 159 </tr> 160 `; 161 } 162 $(".security-questions-table tbody").html(htmlString); 163 } 164} 165 166const pageState = new PageState(); 167const questionSelectize = $("#question").selectize()[0].selectize; 168let mobileItiInstance = window.intlTelInput(document.getElementById("mobile"), { 169 // any initialisation options go here 170 initialCountry: "TZ", 171 formatOnDisplay: true, 172 utilsScript: true, 173 separateDialCode: true, 174 preferredCountries: ["KE", "UG", "TZ"], 175}); 176 177// Initialize dropzone 178const mergedDropzone = new Dropzone("div.dropzone", { 179 url: "/upload-endpoint.php", // Your backend PHP upload endpoint 180 autoProcessQueue: false, 181 paramName: "merged_document", // Field name sent to the server 182 maxFilesize: 5, // Maximum file size in MB 183 maxFiles: 1, // Only accept 1 single merged file 184 acceptedFiles: ".pdf,application/pdf", 185 addRemoveLinks: true, 186 dictDefaultMessage: "<strong>Have a single PDF document?</strong><br>Drop file here or click to upload", 187 188 init: function () { 189 this.on("addedfile", function (file) { 190 // Enforce single file constraint: replace old file if new one is added 191 if (this.files.length > 1) { 192 this.removeFile(this.files[0]); 193 } 194 195 // Auto-assign the single file ID to all 6 hidden document inputs 196 document.querySelectorAll(".individual-doc-input").forEach((input) => { 197 // console.log(input); 198 // input.files = this.files[0]; 199 input.setAttribute("disabled", true); 200 input.setAttribute("readonly", true); 201 console.log("Input Disabled"); 202 }); 203 204 pageState.mergedDocument = file; 205 document.getElementById("individual-document-block").style.display = "flex"; 206 }); 207 208 this.on("success", function (file, response) { 209 // Parse server response (e.g., uploaded file ID) 210 const fileData = typeof response === "string" ? JSON.parse(response) : response; 211 212 // Auto-assign the single file ID to all 6 hidden document inputs 213 document.querySelectorAll(".individual-doc-input").forEach((input) => { 214 console.log(input); 215 input.value = fileData.file_id; 216 input.setAttribute("disabled", true); 217 }); 218 }); 219 220 this.on("removedfile", function () { 221 // Reset individual references if user removes the merged PDF 222 document.querySelectorAll(".individual-doc-input").forEach((input) => { 223 input.removeAttribute("readonly"); 224 input.removeAttribute("disabled"); 225 }); 226 227 document.getElementById("individual-document-block").style.display = "none"; 228 }); 229 } 230 }); 231 232function resetCompanyInfoFields() { 233 for (const fieldId in pageState.companyInfoFields) { 234 pageState.companyInfoFields[fieldId].error = null; 235 $(`.error.${fieldId}`).addClass("d-none"); 236 } 237} 238 239function validateCompanyInfo() { 240 let isValid = true; 241 resetCompanyInfoFields(); 242 for (const fieldId in pageState.companyInfoFields) { 243 let fieldValue = $(`#${fieldId}`).val(); 244 if (!fieldValue) { 245 pageState.companyInfoFields[fieldId].error = pageState.companyInfoFields[fieldId].name + " is required!"; 246 $(`.error.${fieldId}`).text(pageState.companyInfoFields[fieldId].error); 247 $(`.error.${fieldId}`).removeClass("d-none"); 248 isValid = false;
249 } 250 251 else { 252 pageState.companyInfoFields[fieldId].value = fieldValue; 253 } 254 } 255 256 return isValid; 257} 258 259function resetPersonaInfoFields() { 260 for (const fieldId in pageState.personalInfoFields) { 261 pageState.personalInfoFields[fieldId].error = null; 262 $(`.error.${fieldId}`).addClass("d-none"); 263 } 264} 265 266function validatePersonalInfo() { 267 let isValid = true; 268 resetPersonaInfoFields(); 269 for (const fieldId in pageState.personalInfoFields) { 270 let fieldValue = $(`#${fieldId}`).val(); 271 if (!fieldValue) { 272 pageState.personalInfoFields[fieldId].error = pageState.personalInfoFields[fieldId].name + " is required!"; 273 $(`.error.${fieldId}`).text(pageState.personalInfoFields[fieldId].error); 274 $(`.error.${fieldId}`).removeClass("d-none"); 275 isValid = false; 276 } 277 278 else { 279 pageState.personalInfoFields[fieldId].value = fieldValue; 280 } 281 } 282 283 return isValid; 284} 285 286function resetDocumentsError() { 287 $(".error.crt").addClass("d-none"); 288 $(".error.memo").addClass("d-none"); 289 $(".error.tax").addClass("d-none"); 290 $(".error.tin").addClass("d-none"); 291 $(".error.businessLicense").addClass("d-none"); 292 $(".error.insuaranceNote").addClass("d-none"); 293 $(".error.moa").addClass("d-none"); 294 $(".error.aoa").addClass("d-none"); 295 $(".error.terms").addClass("d-none"); 296 $(".acquisitionChannel.error").addClass("d-none"); 297} 298 299function validateDocuments() { 300 let isValid = true; 301 302 resetDocumentsError(); 303 304 let crt = $("#crt").prop("files")[0]; 305 let brela = $("#memo").prop("files")[0]; 306 let tax = $("#tax").prop("files")[0]; 307 let tin = $("#tin").prop("files")[0]; 308 let businessLicense = $("#businessLicense").prop("files")[0]; 309 let insuaranceNote = $("#insuaranceNote").prop("files")[0]; 310 let moa = $("#moa").prop("files")[0]; 311 let aoa = $("#aoa").prop("files")[0]; 312 313 if (!crt) { 314 $(".error.crt").removeClass("d-none"); 315 isValid = false; 316 } 317 318 if (!brela) { 319 $(".error.memo").removeClass("d-none"); 320 isValid = false; 321 } 322 323 if (!tax) { 324 $(".error.tax").removeClass("d-none"); 325 isValid = false; 326 } 327 328 if (!tin) { 329 $(".error.tin").removeClass("d-none"); 330 isValid = false; 331 } 332 333 if (!businessLicense) { 334 $(".error.businessLicense").removeClass("d-none"); 335 isValid = false; 336 } 337 338 if (!insuaranceNote) { 339 $(".error.insuaranceNote").removeClass("d-none"); 340 isValid = false; 341 } 342 343 if (!moa) { 344 $(".error.moa").removeClass("d-none"); 345 isValid = false; 346 } 347 348 if (!aoa) { 349 $(".error.aoa").removeClass("d-none"); 350 isValid = false; 351 } 352 353 if (crt && crt.size / (1024 * 1024) > pageState.maxFileSizeInMB) { 354 $('.error.crt').text('The file upload cannot exceed 5MB'); 355 $('.error.crt').removeClass('d-none'); 356 isValid = false; 357 } 358 359 if (brela && brela.size / (1024 * 1024) > pageState.maxFileSizeInMB) { 360 $('.error.memo').text('The file upload cannot exceed 5MB'); 361 $('.error.memo').removeClass('d-none'); 362 isValid = false; 363 } 364 365 if (tax && tax.size / (1024 * 1024) > pageState.maxFileSizeInMB) { 366 $('.error.tax').text('The file upload cannot exceed 5MB'); 367 $('.error.tax').removeClass('d-none'); 368 isValid = false; 369 } 370 371 if (tin && tin.size / (1024 * 1024) > pageState.maxFileSizeInMB) { 372 $('.error.tin').text('The file upload cannot exceed 5MB'); 373 $('.error.tin').removeClass('d-none'); 374 isValid = false; 375 } 376 377 if (businessLicense && businessLicense.size / (1024 * 1024) > pageState.maxFileSizeInMB) { 378 $('.error.businessLicense').text('The file upload cannot exceed 5MB'); 379 $('.error.businessLicense').removeClass('d-none'); 380 isValid = false; 381 } 382 383 if (insuaranceNote && insuaranceNote.size / (1024 * 1024) > pageState.maxFileSizeInMB) { 384 $('.error.insuaranceNote').text('The file upload cannot exceed 5MB'); 385 $('.error.insuaranceNote').removeClass('d-none'); 386 isValid = false; 387 } 388 389 if (moa && moa.size / (1024 * 1024) > pageState.maxFileSizeInMB) { 390 $('.error.moa').text('The file upload cannot exceed 5MB'); 391 $('.error.moa').removeClass('d-none'); 392 isValid = false; 393 } 394 395 if (aoa && aoa.size / (1024 * 1024) > pageState.maxFileSizeInMB) { 396 $('.error.aoa').text('The file upload cannot exceed 5MB'); 397 $('.error.aoa').removeClass('d-none'); 398 isValid = false;
399 } 400 401 if (!$("#terms").is(":checked")) { 402 $(".error.terms").removeClass("d-none"); 403 isValid = false; 404 } 405 406 if (!$("#acquisitionChannel").val()) { 407 $(".acquisitionChannel.error").removeClass("d-none"); 408 isValid = false; 409 } 410 411 return isValid; 412} 413 414$("#company-info .next-btn").on("click", () => { 415 if (!validateCompanyInfo()) return; 416 pageState.setActiveTab("personal-info"); 417}) 418 419$("#personal-info .next-btn").on("click", () => { 420 $(".error.mobile").addClass("d-none"); 421 $(".error.questions").addClass("d-none"); 422 if (!validatePersonalInfo()) return; 423 if (!mobileItiInstance.isValidNumber()) { 424 $(".error.mobile").removeClass("d-none"); 425 return; 426 } 427 if (Object.keys(pageState.selectedSecurityQuestions).length < 3) { 428 $(".error.questions").removeClass("d-none"); 429 return; 430 } 431 pageState.setActiveTab("documents"); 432}) 433 434// Handle Security Questions 435$("#add-sec-btn").on("click", (e) => { 436 let question = questionSelectize.getValue(); 437 let answer = $("#answer").val(); 438 439 if (!question || !answer) { 440 return; 441 } 442 443 if (pageState.selectedSecurityQuestions[question]) { 444 return; 445 } 446 447 pageState.selectedSecurityQuestions[question] = { 448 question: pageState.securityQuestions[question], 449 answer: answer, 450 }; 451 452 questionSelectize.setValue(""); 453 $("#answer").val(""); 454 455 pageState.setSecurityQuestions(pageState.selectedSecurityQuestions); 456}); 457 458$(".security-questions-table tbody").on("click", ".view-btn", (e) => { 459 const questionId = e.target.dataset.questionId; 460 const action = e.target.classList.contains("show") ? "show" : "hide"; 461 if (action == "show") { 462 $(`tr[data-row-id="${questionId}"]`) 463 .find(".q-answer") 464 .html(pageState.selectedSecurityQuestions[questionId].answer); 465 e.target.classList.replace("show", "hide"); 466 e.target.textContent = "hide"; 467 } else { 468 $(`tr[data-row-id="${questionId}"]`).find(".q-answer").html("****"); 469 e.target.classList.replace("hide", "show"); 470 e.target.textContent = "show"; 471 } 472}); 473 474$(".security-questions-table tbody").on("click", ".remove-btn", (e) => { 475 const questionId = e.target.dataset.questionId; 476 delete pageState.selectedSecurityQuestions[questionId]; 477 pageState.setSecurityQuestions(pageState.selectedSecurityQuestions); 478}); 479 480// Handle Submit 481$("#submit-btn").on("click", (e) => { 482 let isValid = 483 validateCompanyInfo() && validatePersonalInfo(); 484 485 if (!pageState.mergedDocument && !validateDocuments()) { 486 return; 487 } 488 489 $(".error.terms").addClass("d-none"); 490 $(".error.acquisitionChannel").addClass("d-none"); 491 if (!$("#terms").is(":checked")) { 492 $(".error.terms").removeClass("d-none"); 493 isValid = false; 494 } 495 496 if (!$("#acquisitionChannel").val()) { 497 $(".error.acquisitionChannel").removeClass("d-none"); 498 isValid = false; 499 } 500 501 if (!isValid) return; 502 503 let loader = document.createElement("div"); 504 loader.classList.add("spinner", "spinner-grow", "spinner-grow-sm", "ms-2"); 505 e.target.append(loader); 506 e.target.setAttribute("disabled", true); 507 508 // Get Data 509 const formattedSecurityQuestions = {}; 510 for (const index in pageState.selectedSecurityQuestions) { 511 formattedSecurityQuestions[pageState.selectedSecurityQuestions[index]['question']] = pageState.selectedSecurityQuestions[index]['answer']; 512 } 513 const data = { 514 appType: "signUp", 515 firstName: $("#firstName").val(), 516 lastName: $("#lastName").val(), 517 position: $("#position").val(), 518 email: $("#email").val(), 519 mobile: mobileItiInstance.getNumber(), 520 security: JSON.stringify(formattedSecurityQuestions), 521 companyName: $("#companyName").val(), 522 tinNo: $("#tinNo").val(), 523 address: $("#address").val(), 524 businessType: $("#businessType").val(), 525 no: $("#no").val(), 526 no2: $("#no2").val(), 527 acquisitionChannel: $("#acquisitionChannel").val() 528 }; 529 530 let totalFileSizeInBytes = 0; 531 if (!pageState.mergedDocument) { 532 data.crt = $("#crt").prop("files")[0]; 533 data.memo = $("#memo").prop("files")[0]; 534 data.tax = $("#tax").prop("files")[0]; 535 data.tin = $("#tin").prop("files")[0]; 536 data.businessLicense = $("#businessLicense").prop("files")[0]; 537 data.insuaranceNote = $("#insuaranceNote").prop("files")[0]; 538 data.moa = $("#moa").prop("files")[0]; 539 data.aoa = $("#aoa").prop("files")[0]; 540 totalFileSizeInBytes = (data.crt.size + data.memo.size + data.tax.size); 541 } 542 543 else { 544 data.singleDocumentUpload = true; 545 data.mergedDocument = pageState.mergedDocument; 546 } 547 548 const totalFileSizeInMB = (totalFileSizeInBytes / (1024 * 1024)).toFixed(2); 549 if (totalFileSizeInMB > pageState.totalMaxFileSizeInMB) { 550 swal("Warning", "The total file size of the 3 attached documents exceed the limit of 15MB (5MB per file)", "warning"); 551 return; 552 } 553 554 // Post Data 555 postData(data, (json = false), (auth = false)) 556 .then((res) => { 557 if (res.status == "failed") { 558 swal("Failed registration", res.message, "warning"); 559 } else { 560 swal( 561 "Success", 562 "This application will undergo review and you will receive an email", 563 "success" 564 ); 565 566 // Clear form after submission 567 $("input, select, textarea").val(""); 568 questionSelectize.clear(); 569 mobileItiInstance.setNumber(""); 570 pageState.setSecurityQuestions({}); 571 mergedDropzone.removeAllFiles(true); 572 $("#terms").prop("checked", false); 573 document.querySelectorAll(".individual-doc-input").forEach((input) => { 574 input.value = ""; 575 input.removeAttribute("readonly"); 576 input.removeAttribute("disabled"); 577 }); 578 resetCompanyInfoFields(); 579 resetPersonaInfoFields(); 580 resetDocumentsError(); 581 pageState.setActiveTab("company-info"); 582 document.getElementById("individual-document-block").style.display = "none"; 583 } 584 }) 585 .catch(error => { 586 swal("Error", error.message, "error"); 587 }) 588 .finally(() => { 589 loader.remove(); 590 e.target.removeAttribute("disabled"); 591 }); 592});
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.