{
+ let index = self.clone().render_once().unwrap();
+ Ok(index)
+ }
+}
+
+#[get("/panel")]
+pub async fn panel() -> impl Responder {
+ let body = IndexPage::default().run().unwrap();
+ HttpResponse::Ok()
+ .content_type("text/html; charset=utf-8")
+ .body(body)
+}
diff --git a/src/templates/routes.rs b/src/templates/routes.rs
index 9278e632..fbd851e7 100644
--- a/src/templates/routes.rs
+++ b/src/templates/routes.rs
@@ -16,19 +16,13 @@
*/
use sailfish::TemplateOnce;
+//use au
-#[derive(TemplateOnce, Default)]
-#[template(path = "signin.stpl")]
-struct SignIn;
-
-use actix_web::{get, post, web, HttpResponse, Responder};
-//use awc::Client;
-
-#[get("/login/")]
-pub async fn login() -> impl Responder {
- let body = SignIn::default().render_once().unwrap();
- // .map_err(|_| ServiceError::InternalError)?;
- HttpResponse::Ok()
- .content_type("text/html; charset=utf-8")
- .body(body)
-}
+//#[get("/")]
+//pub async fn login() -> impl Responder {
+// let body = SignIn::default().render_once().unwrap();
+// // .map_err(|_| ServiceError::InternalError)?;
+// HttpResponse::Ok()
+// .content_type("text/html; charset=utf-8")
+// .body(body)
+//}
diff --git a/static/bundle/main.css b/static/bundle/main.css
new file mode 100644
index 00000000..31ca5b0d
--- /dev/null
+++ b/static/bundle/main.css
@@ -0,0 +1 @@
+*{padding:0;margin:0}.form__logo{width:110px;padding-top:50px;display:block;margin:auto;transform:translateY(-40.9%)}.form__brand,.form__logo{position:relative;top:20%}.form__brand{padding:10px 0;text-align:center;transform:translateY(-90.9%)}.form-container{max-width:40%;min-width:20%;position:absolute;top:50%;left:50%;transform:translate(-50%,-49.9%);box-sizing:border-box;margin:auto;padding:20px 0}.form__box{border:1px solid #eaecef;background-color:#f6f8fa;border-radius:5px;padding:20px 0}.form__in-group{display:block;position:relative;margin:auto;max-width:80%;padding:10px 0;box-sizing:content-box;align-items:center;align-content:center}.form__in-field{display:block;box-sizing:border-box;margin:10px 0;padding:10px 0;width:100%}.form__in-field--warn{border:1px solid red}.form__in-field--success{border:1px solid #2ea44f}.form__pw-recovery{text-decoration:none;color:#0366d6;font-size:.8rem}.form__submit-button{display:block;border:1px solid #87ceeb;background:#2ea44f;color:#fff;height:40px;border-radius:5px;width:80%;margin:auto}.form__secondary-action{display:block;margin-top:10px}.form__secondary-action__banner{display:block;margin:auto;max-width:80%;text-align:center}.form__secondary-action__link{text-decoration:none;color:#0366d6}
\ No newline at end of file
diff --git a/static/bundle/main.js b/static/bundle/main.js
new file mode 100644
index 00000000..ec9fc730
--- /dev/null
+++ b/static/bundle/main.js
@@ -0,0 +1 @@
+!function(e){var t={};function a(n){if(t[n])return t[n].exports;var r=t[n]={i:n,l:!1,exports:{}};return e[n].call(r.exports,r,r.exports,a),r.l=!0,r.exports}a.m=e,a.c=t,a.d=function(e,t,n){a.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},a.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.t=function(e,t){if(1&t&&(e=a(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(a.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)a.d(n,r,function(t){return e[t]}.bind(null,r));return n},a.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return a.d(t,"a",t),t},a.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},a.p="",a(a.s=1)}([function(e,t,a){},function(e,t,a){"use strict";a.r(t);const n=e=>{if(!e)throw new Error("uri is empty");if("string"!=typeof e)throw new TypeError("URI must be a string");let t=e.length;return"/"==e[t-1]&&(e=e.slice(0,t-1)),e};var r={registerUser:"/api/v1/signup",loginUser:"/api/v1/signin",signoutUser:"/api/v1/signout",deleteAccount:"/api/v1/account/delete",usernameExists:"/api/v1/account/username/exists",emailExists:"/api/v1/account/email/exists",healthCheck:"/api/v1/meta/health",buildDetails:"/api/v1/meta/build",addDomain:"/api/v1/mcaptcha/domain/add",challengeDomain:"/api/v1/mcaptcha/domain/domain/verify/challenge/get",proveDomain:"/api/v1/mcaptcha/domain/domain/verify/challenge/prove",deleteDomain:"/api/v1/mcaptcha/domain/delete",addToken:"/api/v1/mcaptcha/domain/token/add",updateTokenKey:"/api/v1/mcaptcha/domain/token/update",getTokenKey:"/api/v1/mcaptcha/domain/token/get",deleteToken:"/api/v1/mcaptcha/domain/token/delete",addTokenLevels:"/api/v1/mcaptcha/domain/token/levels/add",updateTokenLevels:"/api/v1/mcaptcha/domain/token/levels/update",deleteTokenLevels:"/api/v1/mcaptcha/domain/token/levels/delete",getTokenLevels:"/api/v1/mcaptcha/domain/token/levels/get",getTokenDuration:"/api/v1/mcaptcha/domain/token/token/get",updateTokenDuration:"/api/v1/mcaptcha/domain/token/token/update"};var o=e=>({method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)});a(0);const i=e=>{e.preventDefault();let t=document.getElementById("username").value;o(e,t,"username");let a=document.getElementById("password").value;fetch(r.loginUser,o({username:t,password:a})).then(e=>{e.ok?alert("success"):e.json().then(e=>alert("error: "+e.error))})};var s=async function(){let e=document.getElementById("username"),t={val:e.value},a=await fetch(r.usernameExists,o(t));if(a.ok){let t=await a.json();return t.exists&&(e.className+=" form__in-field--warn",alert("Username taken")),t.exists}{let e=await a.json();alert("error: "+e.error)}return!1};const l=async e=>{e.preventDefault();let t=document.getElementById("username").value;o(e,t,"username");let a=document.getElementById("password").value;if(a!=document.getElementById("password-check").value)return alert("passwords don't match, check again!");let n=document.getElementById("email").value;o(e,n,"email");let i=await checkUsernameExists();if(i)return;if(i=await(async()=>{let e=document.getElementById("email"),t={val:e.value},a=await fetch(r.emailExists,o(t));if(a.ok){let t=await a.json();return t.exists&&(e.className+=" form__in-field--warn",alert("Email taken")),t.exists}{let e=await a.json();alert("error: "+e.error)}})(),i)return;let s={username:t,password:a,email:n},l=await fetch(r.registerUser,o(s));if(l.ok)alert("success");else{let e=await l.json();alert("error: "+e.error)}},u=()=>{},c=new class{constructor(){this.routes=[]}register(e,t){if(!e)throw new Error("uri is empty");if(!t)throw new Error("fn is empty");if("string"!=typeof e)throw new TypeError("URI must be a string");if("function"!=typeof t)throw new TypeError("a callback fn must be provided");this.routes.forEach(t=>{if(t.uri==e)throw new Error(`URI exists. provided URI: ${e}, registered config: ${t}`)});const a={uri:e=n(e),fn:t};this.routes.push(a)}route(){this.routes.forEach(e=>{let t=new RegExp(`^${e.uri}$`),a=window.location.pathname;if(a=n(a),a.match(t))return e.fn.call()})}};c.register("/",()=>{document.getElementById("form").addEventListener("submit",i,!0)}),c.register("/register",()=>{document.getElementById("form").addEventListener("submit",l,!0),document.getElementById("username").addEventListener("input",s,!1)}),c.register("/panel/",u),c.register("/panel/layout.html/",u),c.route()}]);
\ No newline at end of file
diff --git a/static/img/icon-trans.png b/static/img/icon-trans.png
new file mode 100644
index 00000000..db8da876
Binary files /dev/null and b/static/img/icon-trans.png differ
diff --git a/static/img/icon.png b/static/img/icon.png
new file mode 100644
index 00000000..75bf7879
Binary files /dev/null and b/static/img/icon.png differ
diff --git a/static/img/svg/bell.svg b/static/img/svg/bell.svg
new file mode 100644
index 00000000..c0ed0e1e
--- /dev/null
+++ b/static/img/svg/bell.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/static/img/svg/credit-card.svg b/static/img/svg/credit-card.svg
new file mode 100644
index 00000000..9429a635
--- /dev/null
+++ b/static/img/svg/credit-card.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/static/img/svg/eye-off.svg b/static/img/svg/eye-off.svg
new file mode 100644
index 00000000..9d28c182
--- /dev/null
+++ b/static/img/svg/eye-off.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/static/img/svg/eye.svg b/static/img/svg/eye.svg
new file mode 100644
index 00000000..44f107af
--- /dev/null
+++ b/static/img/svg/eye.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/static/img/svg/file-text.svg b/static/img/svg/file-text.svg
new file mode 100644
index 00000000..85186e3b
--- /dev/null
+++ b/static/img/svg/file-text.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/static/img/svg/file.svg b/static/img/svg/file.svg
new file mode 100644
index 00000000..fa1ecc84
--- /dev/null
+++ b/static/img/svg/file.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/static/img/svg/filter.svg b/static/img/svg/filter.svg
new file mode 100644
index 00000000..3ae2cfc7
--- /dev/null
+++ b/static/img/svg/filter.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/static/img/svg/github.svg b/static/img/svg/github.svg
new file mode 100644
index 00000000..8a1c9c0a
--- /dev/null
+++ b/static/img/svg/github.svg
@@ -0,0 +1 @@
+
diff --git a/static/img/svg/globe.svg b/static/img/svg/globe.svg
new file mode 100644
index 00000000..4306cdcb
--- /dev/null
+++ b/static/img/svg/globe.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/static/img/svg/help-circle.svg b/static/img/svg/help-circle.svg
new file mode 100644
index 00000000..1bbf3978
--- /dev/null
+++ b/static/img/svg/help-circle.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/static/img/svg/home.svg b/static/img/svg/home.svg
new file mode 100644
index 00000000..867f0799
--- /dev/null
+++ b/static/img/svg/home.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/static/img/svg/key.svg b/static/img/svg/key.svg
new file mode 100644
index 00000000..e778e74e
--- /dev/null
+++ b/static/img/svg/key.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/static/img/svg/log-out.svg b/static/img/svg/log-out.svg
new file mode 100644
index 00000000..625d2724
--- /dev/null
+++ b/static/img/svg/log-out.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/static/img/svg/menu.svg b/static/img/svg/menu.svg
new file mode 100644
index 00000000..0ea1741e
--- /dev/null
+++ b/static/img/svg/menu.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/static/img/svg/message-square.svg b/static/img/svg/message-square.svg
new file mode 100644
index 00000000..6a2e4e59
--- /dev/null
+++ b/static/img/svg/message-square.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/static/img/svg/moon.svg b/static/img/svg/moon.svg
new file mode 100644
index 00000000..3d94f162
--- /dev/null
+++ b/static/img/svg/moon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/static/img/svg/settings.svg b/static/img/svg/settings.svg
new file mode 100644
index 00000000..0318f2ce
--- /dev/null
+++ b/static/img/svg/settings.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/static/img/svg/shield-off.svg b/static/img/svg/shield-off.svg
new file mode 100644
index 00000000..47805eed
--- /dev/null
+++ b/static/img/svg/shield-off.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/static/img/svg/shield.svg b/static/img/svg/shield.svg
new file mode 100644
index 00000000..18c6ed24
--- /dev/null
+++ b/static/img/svg/shield.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/static/img/svg/tag.svg b/static/img/svg/tag.svg
new file mode 100644
index 00000000..0c7a7704
--- /dev/null
+++ b/static/img/svg/tag.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/static/img/svg/toggle-left.svg b/static/img/svg/toggle-left.svg
new file mode 100644
index 00000000..cd4b4e62
--- /dev/null
+++ b/static/img/svg/toggle-left.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/static/img/svg/toggle-right.svg b/static/img/svg/toggle-right.svg
new file mode 100644
index 00000000..01392ab6
--- /dev/null
+++ b/static/img/svg/toggle-right.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/static/img/svg/user.svg b/static/img/svg/user.svg
new file mode 100644
index 00000000..7b5bc4a7
--- /dev/null
+++ b/static/img/svg/user.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/templates/_reset.scss b/templates/_reset.scss
new file mode 100644
index 00000000..e209c163
--- /dev/null
+++ b/templates/_reset.scss
@@ -0,0 +1,4 @@
+* {
+ padding: 0;
+ margin: 0;
+}
diff --git a/templates/api/v1/routes.js b/templates/api/v1/routes.js
new file mode 100644
index 00000000..d6bc8355
--- /dev/null
+++ b/templates/api/v1/routes.js
@@ -0,0 +1,47 @@
+const ROUTES = {
+ registerUser: '/api/v1/signup',
+
+ loginUser: '/api/v1/signin',
+
+ signoutUser: '/api/v1/signout',
+
+ deleteAccount: '/api/v1/account/delete',
+
+ usernameExists: '/api/v1/account/username/exists',
+
+ emailExists: '/api/v1/account/email/exists',
+
+ healthCheck: '/api/v1/meta/health',
+
+ buildDetails: '/api/v1/meta/build',
+
+ addDomain: '/api/v1/mcaptcha/domain/add',
+
+ challengeDomain: '/api/v1/mcaptcha/domain/domain/verify/challenge/get',
+
+ proveDomain: '/api/v1/mcaptcha/domain/domain/verify/challenge/prove',
+
+ deleteDomain: '/api/v1/mcaptcha/domain/delete',
+
+ addToken: '/api/v1/mcaptcha/domain/token/add',
+
+ updateTokenKey: '/api/v1/mcaptcha/domain/token/update',
+
+ getTokenKey: '/api/v1/mcaptcha/domain/token/get',
+
+ deleteToken: '/api/v1/mcaptcha/domain/token/delete',
+
+ addTokenLevels: '/api/v1/mcaptcha/domain/token/levels/add',
+
+ updateTokenLevels: '/api/v1/mcaptcha/domain/token/levels/update',
+
+ deleteTokenLevels: '/api/v1/mcaptcha/domain/token/levels/delete',
+
+ getTokenLevels: '/api/v1/mcaptcha/domain/token/levels/get',
+
+ getTokenDuration: '/api/v1/mcaptcha/domain/token/token/get',
+
+ updateTokenDuration: '/api/v1/mcaptcha/domain/token/token/update',
+};
+
+export default ROUTES;
diff --git a/templates/auth/forms.scss b/templates/auth/forms.scss
new file mode 100644
index 00000000..61ef9d5d
--- /dev/null
+++ b/templates/auth/forms.scss
@@ -0,0 +1,101 @@
+@import '../reset';
+
+.form__logo {
+ width: 110px;
+ padding-top: 50px;
+ display: block;
+ margin: auto;
+ position: relative;
+ top: 20%;
+ transform: translate(0%, -40.9%);
+}
+
+.form__brand {
+ padding: 10px 0;
+ text-align: center;
+ position: relative;
+ top: 20%;
+ transform: translate(0%, -90.9%);
+}
+
+.form-container {
+ max-width: 40%;
+ min-width: 20%;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -49.9%);
+ box-sizing: border-box;
+ margin: auto;
+ padding: 20px 0;
+}
+
+.form__box {
+ border: 1px solid #eaecef;
+ background-color: #f6f8fa;
+ border-radius: 5px;
+ padding: 20px 0;
+}
+
+.form__in-group {
+ display: block;
+ position: relative;
+ margin: auto;
+ max-width: 80%;
+ padding: 10px 0px;
+
+ box-sizing: content-box;
+
+ align-items: center;
+ align-content: center;
+}
+
+.form__in-field {
+ display: block;
+ box-sizing: border-box;
+ margin: 10px 0;
+ padding: 10px 0;
+ width: 100%;
+}
+
+.form__in-field--warn {
+ border: solid 1px red;
+}
+
+.form__in-field--success {
+ border: solid 1px #2ea44f;
+}
+
+.form__pw-recovery {
+ text-decoration: none;
+ color: rgb(3, 102, 214);
+ font-size: 0.8rem;
+}
+
+.form__submit-button {
+ display: block;
+ border: 1px solid skyblue;
+ background: #2ea44f;
+ color: white;
+ height: 40px;
+ border-radius: 5px;
+ width: 80%;
+ margin: auto;
+}
+
+.form__secondary-action {
+ display: block;
+ margin-top: 10px;
+}
+
+.form__secondary-action__banner {
+ display: block;
+ margin: auto;
+ max-width: 80%;
+ text-align: center;
+}
+
+.form__secondary-action__link {
+ text-decoration: none;
+ color: rgb(3, 102, 214);
+}
diff --git a/templates/auth/login/index.html b/templates/auth/login/index.html
new file mode 100644
index 00000000..a6db4773
--- /dev/null
+++ b/templates/auth/login/index.html
@@ -0,0 +1,46 @@
+<. include!("../../components/headers.html"); .>
+
+<. include!("../../components/footers.html"); .>
diff --git a/templates/auth/login/index.js b/templates/auth/login/index.js
new file mode 100644
index 00000000..5d5b557e
--- /dev/null
+++ b/templates/auth/login/index.js
@@ -0,0 +1,31 @@
+import ROUTES from '../../api/v1/routes';
+
+import isBlankString from '../../utils/genJsonPayload';
+import genJsonPayload from '../../utils/genJsonPayload';
+
+import '../forms.scss';
+
+const login = e => {
+ e.preventDefault();
+ let username = document.getElementById('username').value;
+ isBlankString(e, username, 'username');
+
+ let password = document.getElementById('password').value;
+ let payload = {
+ username,
+ password,
+ };
+
+ fetch(ROUTES.loginUser, genJsonPayload(payload)).then(res => {
+ if (res.ok) {
+ alert('success');
+ } else {
+ res.json().then(err => alert(`error: ${err.error}`));
+ }
+ });
+};
+
+export const index = () => {
+ let form = document.getElementById('form');
+ form.addEventListener('submit', login, true);
+};
diff --git a/templates/auth/register/emailExists.js b/templates/auth/register/emailExists.js
new file mode 100644
index 00000000..f1b079c2
--- /dev/null
+++ b/templates/auth/register/emailExists.js
@@ -0,0 +1,43 @@
+import ROUTES from '../../api/v1/routes';
+
+import genJsonPayload from '../../utils/genJsonPayload';
+
+const checkEmailExists = async () => {
+ let email = document.getElementById('email');
+ let val = email.value;
+ let payload = {
+ val,
+ };
+
+ // return fetch(ROUTES.emailExists, genJsonPayload(payload)).then(res => {
+ // if (res.ok) {
+ // res.json().then(data => {
+ // if (data.exists) {
+ // console.log(email.className);
+ // email.className += ' form__in-field--warn';
+ // alert('Email taken');
+ // }
+ //
+ // return data.exists;
+ // });
+ // } else {
+ // res.json().then(err => alert(`error: ${err.error}`));
+ // }
+ // });
+ //
+
+ let res = await fetch(ROUTES.emailExists, genJsonPayload(payload));
+ if (res.ok) {
+ let data = await res.json();
+ if (data.exists) {
+ email.className += ' form__in-field--warn';
+ alert('Email taken');
+ }
+ return data.exists;
+ } else {
+ let err = await res.json();
+ alert(`error: ${err.error}`);
+ }
+};
+
+export {checkEmailExists};
diff --git a/templates/auth/register/index.html b/templates/auth/register/index.html
new file mode 100644
index 00000000..142597d8
--- /dev/null
+++ b/templates/auth/register/index.html
@@ -0,0 +1,65 @@
+<. include!("../../components/headers.html"); .>
+
+<. include!("../../components/footers.html"); .>
diff --git a/templates/auth/register/index.js b/templates/auth/register/index.js
new file mode 100644
index 00000000..8358dbb7
--- /dev/null
+++ b/templates/auth/register/index.js
@@ -0,0 +1,57 @@
+import ROUTES from '../../api/v1/routes';
+
+import isBlankString from '../../utils/genJsonPayload';
+import genJsonPayload from '../../utils/genJsonPayload';
+
+import userExists from './userExists';
+import {checkEmailExists} from './emailExists';
+
+import '../forms.scss';
+
+const registerUser = async e => {
+ e.preventDefault();
+
+ let username = document.getElementById('username').value;
+ isBlankString(e, username, 'username');
+
+ let password = document.getElementById('password').value;
+ let passwordCheck = document.getElementById('password-check').value;
+ if (password != passwordCheck) {
+ return alert("passwords don't match, check again!");
+ }
+
+ let email = document.getElementById('email').value;
+ isBlankString(e, email, 'email');
+
+ let exists = await checkUsernameExists();
+ if (exists) {
+ return;
+ }
+
+ exists = await checkEmailExists();
+ if (exists) {
+ return;
+ }
+
+ let payload = {
+ username,
+ password,
+ email,
+ };
+
+ let res = await fetch(ROUTES.registerUser, genJsonPayload(payload));
+ if (res.ok) {
+ alert('success');
+ } else {
+ let err = await res.json();
+ alert(`error: ${err.error}`);
+ }
+};
+
+export const index = () => {
+ let form = document.getElementById('form');
+ form.addEventListener('submit', registerUser, true);
+
+ let username = document.getElementById('username');
+ username.addEventListener('input', userExists, false);
+}
diff --git a/templates/auth/register/userExists.js b/templates/auth/register/userExists.js
new file mode 100644
index 00000000..ba2277dd
--- /dev/null
+++ b/templates/auth/register/userExists.js
@@ -0,0 +1,44 @@
+import ROUTES from '../../api/v1/routes';
+
+import genJsonPayload from '../../utils/genJsonPayload';
+
+
+//export const checkUsernameExists = async () => {
+async function userExists() {
+ let username = document.getElementById('username');
+ let val = username.value;
+ let payload = {
+ val,
+ };
+
+ // return fetch(ROUTES.usernameExists, genJsonPayload(payload)).then(res => {
+ // if (res.ok) {
+ // res.json().then(data => {
+ // if (data.exists) {
+ // username.className += ' form__in-field--warn';
+ // alert('Username taken');
+ // }
+ // return data.exists;
+ // });
+ // } else {
+ // res.json().then(err => alert(`error: ${err.error}`));
+ // }
+ // });
+ //
+
+ let res = await fetch(ROUTES.usernameExists, genJsonPayload(payload));
+ if (res.ok) {
+ let data = await res.json();
+ if (data.exists) {
+ username.className += ' form__in-field--warn';
+ alert('Username taken');
+ }
+ return data.exists;
+ } else {
+ let err = await res.json();
+ alert(`error: ${err.error}`);
+ }
+ return false;
+};
+
+export default userExists;
diff --git a/templates/components/footers.html b/templates/components/footers.html
new file mode 100644
index 00000000..4ca2a61d
--- /dev/null
+++ b/templates/components/footers.html
@@ -0,0 +1,4 @@
+