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