Landing: Implement registration success/failure flows

Change-Id: I0b48cfb0c0b35bfe7c71b13f8953951821fb3958
diff --git a/apps/landing/static/js/main.js b/apps/landing/static/js/main.js
index 24e651e..a068560 100644
--- a/apps/landing/static/js/main.js
+++ b/apps/landing/static/js/main.js
@@ -139,55 +139,3 @@
         fact.addEventListener("mouseover", () => handleMouseover(facts[index].params.image, index, facts[index].params.title));
     });
 });
-
-async function loadPublicData() {
-  let networkSelect = document.querySelector("select#network");
-  if (networkSelect === undefined) {
-	return;
-  }
-  networkSelect.innerHTML = "";
-  let appTypeSelect = document.querySelector("select#app-type");
-  if (appTypeSelect === undefined) {
-	return;
-  }
-  appTypeSelect.innerHTML = "";
-  let resp = await fetch("https://app.v1.dodo.cloud/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 register() {
-  var data = {
-	type: document.getElementById("app-type").value,
-	adminPublicKey: document.getElementById("public-key").value,
-	network: document.getElementById("network").value,
-	subdomain: document.getElementById("subdomain").value,
-  };
-  fetch("https://app.v1.dodo.cloud/api/apps", {
-	method: "POST",
-	body: JSON.stringify(data),
-  }).then((resp) => {
-	resp.json().then((r) => {
-	  console.log(r);
-	});
-  });
-  return false;
-}
-
-document.addEventListener("DOMContentLoaded", () => {
-  loadPublicData();
-});
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;
+    }
+});
diff --git a/apps/landing/static/styles/style.css b/apps/landing/static/styles/style.css
index 6537d58..8cd8e34 100644
--- a/apps/landing/static/styles/style.css
+++ b/apps/landing/static/styles/style.css
@@ -18,7 +18,9 @@
   font-size: var(--fontSize);
 }
 
-input, textarea, select {
+input,
+textarea,
+select {
   font-family: Hack, monospace;
   font-size: var(--fontSize);
 }
@@ -291,6 +293,8 @@
   padding: 20px;
   display: flex;
   justify-content: center;
+  flex-direction: column;
+  align-items: center;
 }
 
 .form-container-footer {
@@ -309,14 +313,12 @@
 .form-group-footer {
   display: flex;
   justify-content: space-between;
-  align-items: center;
+  align-items: flex-start;
   flex-direction: column;
 }
 
 .form-group-footer label {
   display: block;
-  margin-bottom: 5px;
-  padding-bottom: 10px;
   color: var(--formText);
   margin-right: auto;
   width: 100%;
@@ -325,8 +327,7 @@
 .form-group-footer input,
 .form-group-footer textarea {
   width: 100%;
-  padding: 10px;
-  margin-top: 5px;
+  padding: 10px 10px 10px 1rem;
   border: 1px solid var(--formText);
   box-sizing: border-box;
   color: var(--formText);
@@ -334,8 +335,32 @@
   resize: vertical;
 }
 
+.form-group-footer textarea {
+  margin-top: 10px;
+}
+
+.form-group-footer select {
+  padding: 10px 2.5rem 10px 1rem;
+  padding-inline-start: 1rem;
+  padding-inline-end: 2.5rem;
+  border: 1px solid var(--formText);
+  border-radius: 0;
+  outline: 0;
+  box-shadow: none;
+  background-color: var(--formBg);
+  -webkit-appearance: none;
+  -moz-appearance: none;
+  appearance: none;
+  box-sizing: border-box;
+  background-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='24'%20height='24'%20viewBox='0%200%2024%2024'%20fill='none'%20stroke='rgb(136%2C%20145%2C%20164)'%20stroke-width='2'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpolyline%20points='6%209%2012%2015%2018%209'%20/%3E%3C/svg%3E");
+  background-repeat: no-repeat;
+  background-position: center right 0.75rem;
+  background-size: 1rem auto;
+}
+
 .form-group-footer input:focus,
-.form-group-footer textarea:focus {
+.form-group-footer textarea:focus,
+.form-group-footer select:focus {
   outline: none !important;
   border: 1px solid var(--button);
 }
@@ -355,6 +380,44 @@
   background-color: #d4888d;
 }
 
+input::placeholder,
+textarea::placeholder {
+  color: rgba(214, 214, 214, 0.6);
+  opacity: 1;
+}
+
+select option[disabled] {
+  color: rgba(214, 214, 214, 0.6);
+}
+
+select {
+  color: rgb(214, 214, 214);
+}
+
+select:invalid {
+  color: rgba(214, 214, 214, 0.6);
+}
+
+select:not(:invalid) {
+  color: rgb(214, 214, 214);
+}
+
+select option:not(:disabled) {
+  color: rgb(214, 214, 214);
+}
+
+input:disabled,
+select:disabled,
+textarea:disabled {
+  color: rgba(214, 214, 214, 0.6);
+  cursor: not-allowed;
+}
+
+button:disabled {
+  cursor: not-allowed;
+  opacity: 0.6;
+}
+
 /* FOOTER FORM END */
 
 /* APPS START */
@@ -457,3 +520,117 @@
 }
 
 /* ABOUT END */
+
+/* SECCESFULL REGISTRATION START */
+
+.registration-outcome {
+  display: flex;
+  flex-direction: column;
+  align-items: stretch;
+}
+
+.registration-outcome button {
+  font-size: 16px;
+  cursor: pointer;
+  background-color: var(--bg);
+  border-radius: 0;
+  border: 0;
+  height: 30px;
+  font-family: Hack, monospace;
+  color: #3a3a3a;
+  display: flex;
+  align-items: center;
+  justify-content: start;
+}
+
+.registration-outcome h3 {
+  margin: 0;
+  margin-bottom: 15px;
+}
+
+.registration-outcome .pass {
+  display: flex;
+}
+
+.registration-outcome svg {
+  color: var(--button);
+  margin-left: 5px;
+}
+
+.tooltip {
+  position: absolute;
+  background-color: #333;
+  color: #fff;
+  padding: 5px 10px;
+  border-radius: 4px;
+  white-space: nowrap;
+  opacity: 0;
+  visibility: hidden;
+  transition: opacity 0.3s;
+  font-size: 14px;
+}
+
+/* SECCESFULL REGISTRATION END */
+
+.reg-inputs {
+  display: grid;
+  grid-template-columns: 1fr 1fr 1fr;
+  gap: 10px;
+  margin-bottom: 10px;
+  width: 100%;
+}
+
+@media (max-width: 768px) {
+  .reg-inputs {
+    grid-template-columns: 1fr;
+    width: 100%;
+  }
+}
+
+#create-app-button {
+  display: flex;
+  align-items: center;
+  margin-top: 5px;
+}
+
+#error-message {
+  margin: 0 0 10px 0;
+  color: var(--logo);
+  display: none;
+}
+
+.animated-spinner g {
+  animation: rotate 2s linear infinite;
+  transform-origin: center center;
+}
+
+.animated-spinner circle {
+  stroke-dasharray: 75, 100;
+  stroke-dashoffset: -5;
+  animation: dash 1.5s ease-in-out infinite;
+  stroke-linecap: round;
+}
+
+@keyframes rotate {
+  0% {
+    transform: rotate(0deg);
+  }
+  100% {
+    transform: rotate(360deg);
+  }
+}
+
+@keyframes dash {
+  0% {
+    stroke-dasharray: 1, 100;
+    stroke-dashoffset: 0;
+  }
+  50% {
+    stroke-dasharray: 44.5, 100;
+    stroke-dashoffset: -17.5;
+  }
+  100% {
+    stroke-dasharray: 44.5, 100;
+    stroke-dashoffset: -62;
+  }
+}