Landing: Implement registration success/failure flows

Change-Id: I0b48cfb0c0b35bfe7c71b13f8953951821fb3958
diff --git a/apps/landing/static/js/register.js b/apps/landing/static/js/register.js
new file mode 100644
index 0000000..ca66f0b
--- /dev/null
+++ b/apps/landing/static/js/register.js
@@ -0,0 +1,128 @@
+async function loadPublicData() {
+    let networkSelect = document.querySelector("select#network");
+    if (networkSelect === undefined) {
+        return;
+    }
+    let appTypeSelect = document.querySelector("select#app-type");
+    if (appTypeSelect === undefined) {
+        return;
+    }
+    networkSelect.innerHTML = `<option value="" disabled selected>domain</option>`;
+    appTypeSelect.innerHTML = `<option value="" disabled selected>application type</option>`;
+    let resp = await fetch(`${apiBaseURL}/api/public-data`);
+    if (!resp.ok) {
+        return;
+    }
+    let data = await resp.json();
+    data.networks.forEach((network) => {
+        let opt = document.createElement("option");
+        opt.setAttribute("value", network.domain);
+        opt.innerHTML = network.domain;
+        networkSelect.appendChild(opt);
+    });
+    data.types.forEach((t) => {
+        let opt = document.createElement("option");
+        opt.setAttribute("value", t);
+        opt.innerHTML = t;
+        appTypeSelect.appendChild(opt);
+    });
+}
+
+function errorRender(error) {
+    const errorMsg = document.getElementById("error-message");
+    errorMsg.innerHTML = error;
+    errorMsg.style.display = "block";
+}
+
+function triggerForm(status, errDisplay, buttonTxt, spinnerStatus) {
+    const form = document.getElementById('register-form');
+    const elements = form.querySelectorAll('input, select, textarea, button');
+    elements.forEach(element => {
+        element.disabled = status;
+    });
+    const errorMsg = document.getElementById("error-message");
+    errorMsg.style.display = errDisplay;
+    const button = document.getElementById("create-app-button");
+    button.removeChild(button.lastChild);
+    button.appendChild(document.createTextNode(buttonTxt));
+    const spinner = document.getElementById("spinner");
+    spinner.style.display = spinnerStatus;
+}
+
+async function register(event) {
+    event.preventDefault();
+    const data = {
+        type: document.getElementById("app-type").value,
+        adminPublicKey: document.getElementById("public-key").value,
+        network: document.getElementById("network").value,
+        subdomain: document.getElementById("subdomain").value,
+    };
+    triggerForm(true, "none", "\u00A0\u00A0\creating first app", "inline-block");
+    fetch(`${apiBaseURL}/api/apps`, {
+        method: "POST",
+        body: JSON.stringify(data)
+    })
+        .then(response => {
+            if (!response.ok) {
+                errorRender("Internal error, try again");
+                triggerForm(false, "block", "create first app", "none");
+            }
+            return response.json();
+        })
+        .then(result => {
+            const domain = document.getElementById("network").value;
+            const subdomain = document.getElementById("subdomain").value;
+            const appLink = `https://${subdomain}.${domain}`;
+            const appStatusLink = `https://status.${subdomain}.${domain}`;
+
+            const successHTML = `
+            <div class="registration-outcome">
+                <h3>Application has been successfully deployed, use information below to access it:</h3>
+                <button onclick="window.open('${appLink}', '_blank')">Application address: ${appLink}</button>
+                <br>
+                <button onclick="window.open('${appStatusLink}', '_blank')">Status page address: ${appStatusLink}</button>
+                <br>
+                <button class="pass" onclick="copyPassword('${result.password}')" id="copy-button">
+                    Status page password:&nbsp;<strong>${result.password}</strong> 
+                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 256 256">
+                        <path fill="currentColor" d="M216 32H88a8 8 0 0 0-8 8v40H40a8 8 0 0 0-8 8v128a8 8 0 0 0 8 8h128a8 8 0 0 0 8-8v-40h40a8 8 0 0 0 8-8V40a8 8 0 0 0-8-8m-56 176H48V96h112Zm48-48h-32V88a8 8 0 0 0-8-8H96V48h112Z" />
+                    </svg>
+                </button>
+                <div id="tooltip" class="tooltip">Password copied to clipboard</div>
+            </div>`;
+            document.getElementById("form-container").innerHTML = successHTML;
+        })
+        .catch(error => {
+            errorRender(`Failed to deploy application. Error: '${error.message}'`);
+            triggerForm(false, "block", "create first app", "none");
+        })
+        .finally(() => {
+            document.getElementById("spinner").style.display = "none";
+        });
+    return;
+}
+
+function copyPassword(password) {
+    navigator.clipboard.writeText(password).then(() => {
+        const button = document.getElementById("copy-button");
+        const tooltip = document.getElementById("tooltip");
+        const rect = button.getBoundingClientRect();
+        const tooltipWidth = tooltip.offsetWidth;
+        tooltip.style.top = `${rect.top - 30 + window.scrollY}px`;
+        tooltip.style.left = `${rect.left + (rect.width / 2) - (tooltipWidth / 2) + window.scrollX}px`;
+        tooltip.style.opacity = "1";
+        tooltip.style.visibility = "visible";
+        setTimeout(() => {
+            tooltip.style.opacity = "0";
+            tooltip.style.visibility = "hidden";
+        }, 1000);
+    });
+}
+
+document.addEventListener("DOMContentLoaded", () => {
+    loadPublicData();
+    const registerForm = document.getElementById("register-form");
+    if (registerForm) {
+        registerForm.onsubmit = register;
+    }
+});