diff --git a/.gitignore b/.gitignore index add34550..dba25a01 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,8 @@ infra_options.json _deploy.sh HackUCF.ovpn config.yml +*.pem +.env +tests/.env +.venv/ +onboardlite.egg-info/ diff --git a/app/forms/2.json b/app/forms/2.json index ae29ff0f..f3e6fbf5 100644 --- a/app/forms/2.json +++ b/app/forms/2.json @@ -37,7 +37,7 @@ }, { "label": "NID", - "caption": "This is the UCF identifier with two letters and six numbers. It's in your school email!", + "caption": "This is the UCF identifier with two letters and six numbers. It's in your school email! (Format: ab123456)", "input": "nid", "key": "nid", "required": true diff --git a/app/forms/edit.json b/app/forms/edit.json index bcc55ba9..cd6476b5 100644 --- a/app/forms/edit.json +++ b/app/forms/edit.json @@ -26,7 +26,7 @@ }, { "label": "NID", - "caption": "This is the UCF identifier with two letters and six numbers. It's in your school email!", + "caption": "This is the UCF identifier with two letters and six numbers. It's in your school email! (Format: ab123456)", "input": "nid", "key": "nid", "required": true diff --git a/app/static/form.js b/app/static/form.js index b3a59b79..fe5dd249 100644 --- a/app/static/form.js +++ b/app/static/form.js @@ -141,42 +141,134 @@ function get_body() { return body; } +// NID validation and formatting functions +function isValidNID(nid) { + // UCF NID format: 2 lowercase letters followed by 6 digits + const nidPattern = /^([a-z]{2}[0-9]{6})$/; + return nidPattern.test(nid); +} + +function formatNIDValue(value) { + if (!value) return value; + // Convert to lowercase and remove any non-alphanumeric characters + return value.toLowerCase().replace(/[^a-z0-9]/g, ''); +} + +function getSpecificNIDError(formattedValue) { + if (!formattedValue) { + return " (required - format: ab123456)"; + } + + if (formattedValue.length < 8) { + return ` (too short - need ${8 - formattedValue.length} more characters)`; + } + + if (formattedValue.length > 8) { + return " (too long - should be 8 characters)"; + } + + // Check specific pattern issues + const firstTwoChars = formattedValue.substring(0, 2); + const lastSixChars = formattedValue.substring(2); + + if (!/^[a-z]{2}$/.test(firstTwoChars)) { + return " (must start with 2 letters)"; + } + + if (!/^[0-9]{6}$/.test(lastSixChars)) { + return " (must end with 6 numbers)"; + } + + return " (invalid format - use ab123456)"; +} + +function validateNIDField(element, is_loud) { + const value = element.value; + const formattedValue = formatNIDValue(value); + + // Auto-format the field value as the user types + if (value !== formattedValue) { + element.value = formattedValue; + } + + const isValid = formattedValue && isValidNID(formattedValue); + + if (is_loud) { + // Clear all previous error messages + if (element.placeholder) { + element.placeholder = element.placeholder + .replaceAll(" (required!)", "") + .replaceAll(" (invalid format!)", "") + .replaceAll(" (required - format: ab123456)", "") + .replace(/ \(too short - need \d+ more characters?\)/g, "") + .replaceAll(" (too long - should be 8 characters)", "") + .replaceAll(" (must start with 2 letters)", "") + .replaceAll(" (must end with 6 numbers)", "") + .replaceAll(" (invalid format - use ab123456)", ""); + } + + if (!isValid) { + element.style.background = "var(--hackucf-error)"; + element.style.color = "white"; + if (element.placeholder) { + element.placeholder += getSpecificNIDError(formattedValue); + } + } else { + // Reset styling for valid input + element.style.background = "var(--hackucf-off-white)"; + element.style.color = "black"; + } + } + + return isValid; +} + function validate_required(is_loud) { const els = document.querySelectorAll("[required]"); let result = true; for (let i = 0; i < els.length; i++) { - let value = get_value(els[i]); + let element = els[i]; + + // Special handling for NID fields + if (element.nodeName === "INPUT" && element.name === "nid") { + if (!validateNIDField(element, is_loud)) { + result = false; + } + continue; + } + + let value = get_value(element); // Undefined checks - if (typeof value == "undefined" || !RegExp(els[i].pattern).test(value)) { + if (typeof value == "undefined" || !RegExp(element.pattern).test(value)) { // is_loud makes us populate 'validation required' texts. if (is_loud) { - if (els[i].nodeName == "FIELDSET") { - els[i].style.color = "var(--hackucf-error)"; - els[i].style.fontWeight = "bold"; + if (element.nodeName == "FIELDSET") { + element.style.color = "var(--hackucf-error)"; + element.style.fontWeight = "bold"; } else { - els[i].style.background = "var(--hackucf-error)"; - els[i].style.color = "white"; - if (els[i].placeholder) { - els[i].placeholder = els[i].placeholder.replaceAll( + element.style.background = "var(--hackucf-error)"; + element.style.color = "white"; + if (element.placeholder) { + element.placeholder = element.placeholder.replaceAll( " (required!)", "", ); - els[i].placeholder += " (required!)"; + element.placeholder += " (required!)"; } } } result = false; } else if (is_loud) { // Revert previous style changes if input filled out. - if (els[i].nodeName == "FIELDSET") { - els[i].style.color = "var(--text)"; - els[i].style.fontWeight = "normal"; + if (element.nodeName == "FIELDSET") { + element.style.color = "var(--text)"; + element.style.fontWeight = "normal"; } else { - els[i].style.background = "var(--hackucf-off-white)"; - els[i].style.color = "black"; - if (els[i].placeholder) { - els[i].placeholder = els[i].placeholder.replaceAll( + element.style.background = "var(--hackucf-off-white)"; + element.style.color = "black"; + if (element.placeholder) { + element.placeholder = element.placeholder.replaceAll( " (required!)", "", ); @@ -336,6 +428,44 @@ window.onload = (evt) => { document.getElementById("apple_wallet").style.display = "block"; } + // Add real-time validation for NID fields + const nidInputs = document.querySelectorAll('input[name="nid"]'); + nidInputs.forEach(function(nidInput) { + nidInput.addEventListener('input', function(event) { + const formatted = formatNIDValue(event.target.value); + if (event.target.value !== formatted) { + event.target.value = formatted; + } + + // Real-time validation feedback with specific error messages + if (formatted.length > 0) { + if (isValidNID(formatted)) { + event.target.style.background = "var(--hackucf-off-white)"; + event.target.style.color = "black"; + // Clear any error messages from placeholder + if (event.target.placeholder) { + event.target.placeholder = event.target.placeholder + .replaceAll(" (required - format: ab123456)", "") + .replace(/ \(too short - need \d+ more characters?\)/g, "") + .replaceAll(" (too long - should be 8 characters)", "") + .replaceAll(" (must start with 2 letters)", "") + .replaceAll(" (must end with 6 numbers)", "") + .replaceAll(" (invalid format - use ab123456)", ""); + } + } else { + // Show gentle feedback while typing, more specific on blur + event.target.style.background = "#ffeeee"; + event.target.style.color = "black"; + } + } + }); + + nidInput.addEventListener('blur', function(event) { + // Validate on blur (when user leaves the field) with specific error messages + validateNIDField(event.target, true); + }); + }); + // Infra checker fetch("https://horizon.hackucf.org", { mode: "no-cors" }) .then((evt) => {