From 312d02a41dff40e270fd4cf7aef9bfd99afe10d3 Mon Sep 17 00:00:00 2001 From: realaravinth Date: Mon, 5 Apr 2021 17:26:58 +0530 Subject: [PATCH] frontend restructured --- frontend/.gitignore | 1 + frontend/package.json | 1 + frontend/src/main.rs | 6 +- frontend/templates/_reset.scss | 4 + frontend/templates/api/v1/routes.js | 47 ++++++ frontend/templates/auth/forms.scss | 101 ++++++++++++ .../templates/{ => auth/login}/index.html | 6 +- frontend/templates/auth/login/index.js | 26 ++++ .../templates/auth/register/emailExists.js | 43 +++++ .../{signup => auth/register}/index.html | 4 +- frontend/templates/auth/register/index.js | 55 +++++++ .../templates/auth/register/userExists.js | 48 ++++++ frontend/templates/index.js | 21 +++ frontend/templates/panel/index.js | 67 ++++++++ frontend/templates/panel/main.scss | 147 ++++++++++++++++++ frontend/templates/utils/genJsonPayload.js | 12 ++ frontend/templates/utils/isBlankString.js | 8 + frontend/webpack.common.js | 2 +- frontend/webpack.dev.js | 4 +- frontend/webpack.prod.js | 4 +- 20 files changed, 594 insertions(+), 13 deletions(-) create mode 100644 frontend/templates/_reset.scss create mode 100644 frontend/templates/api/v1/routes.js create mode 100644 frontend/templates/auth/forms.scss rename frontend/templates/{ => auth/login}/index.html (83%) create mode 100644 frontend/templates/auth/login/index.js create mode 100644 frontend/templates/auth/register/emailExists.js rename frontend/templates/{signup => auth/register}/index.html (93%) create mode 100644 frontend/templates/auth/register/index.js create mode 100644 frontend/templates/auth/register/userExists.js create mode 100644 frontend/templates/index.js create mode 100644 frontend/templates/panel/index.js create mode 100644 frontend/templates/panel/main.scss create mode 100644 frontend/templates/utils/genJsonPayload.js create mode 100644 frontend/templates/utils/isBlankString.js diff --git a/frontend/.gitignore b/frontend/.gitignore index ba827864..aa356810 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -3,3 +3,4 @@ dist/ /target .html output/ +static diff --git a/frontend/package.json b/frontend/package.json index 2d06e2c6..cb26b1a1 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,6 +10,7 @@ "start": "find . | entr yarn build-dev", "build-dev": "cargo run && webpack --config webpack.dev.js", "build-fast-dev": "cargo run && webpack-dev-server --config webpack.dev.js", + "build-check": "webpack-dev-server --config webpack.dev.js", "build": "cargo run && webpack --config webpack.prod.js" }, "private": true, diff --git a/frontend/src/main.rs b/frontend/src/main.rs index 7b37af81..a4f9cff8 100644 --- a/frontend/src/main.rs +++ b/frontend/src/main.rs @@ -4,7 +4,7 @@ use tokio::fs; use tokio::io::{Error, ErrorKind}; #[derive(Clone, TemplateOnce)] -#[template(path = "index.html")] +#[template(path = "auth/login/index.html")] struct IndexPage { name: String, title: String, @@ -66,7 +66,7 @@ mod signup { use super::*; #[derive(TemplateOnce, Clone)] - #[template(path = "signup/index.html")] + #[template(path = "auth/register/index.html")] pub struct IndexPage { pub name: String, pub title: String, @@ -83,7 +83,7 @@ mod signup { impl IndexPage { pub async fn run(&self) -> Result<(), Error> { - let dir = root_path("signup"); + let dir = root_path("register"); let file = rel_path(&dir, "index.html"); print!(""); diff --git a/frontend/templates/_reset.scss b/frontend/templates/_reset.scss new file mode 100644 index 00000000..e209c163 --- /dev/null +++ b/frontend/templates/_reset.scss @@ -0,0 +1,4 @@ +* { + padding: 0; + margin: 0; +} diff --git a/frontend/templates/api/v1/routes.js b/frontend/templates/api/v1/routes.js new file mode 100644 index 00000000..d6bc8355 --- /dev/null +++ b/frontend/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/frontend/templates/auth/forms.scss b/frontend/templates/auth/forms.scss new file mode 100644 index 00000000..61ef9d5d --- /dev/null +++ b/frontend/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/frontend/templates/index.html b/frontend/templates/auth/login/index.html similarity index 83% rename from frontend/templates/index.html rename to frontend/templates/auth/login/index.html index b6754253..efd4dcc2 100644 --- a/frontend/templates/index.html +++ b/frontend/templates/auth/login/index.html @@ -1,4 +1,4 @@ -<. include!("components/headers.html"); .> +<. include!("../../components/headers.html"); .>

Sign in to mCaptcha

@@ -23,8 +23,8 @@

New to mCaptcha? - Create account + Create account

- +<. include!("../../components/footers.html"); .> diff --git a/frontend/templates/auth/login/index.js b/frontend/templates/auth/login/index.js new file mode 100644 index 00000000..6ab771bd --- /dev/null +++ b/frontend/templates/auth/login/index.js @@ -0,0 +1,26 @@ +import ROUTES from '../../api/v1/routes'; + +import isBlankString from '../../utils/genJsonPayload'; +import genJsonPayload from '../../utils/genJsonPayload'; + +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 default login; diff --git a/frontend/templates/auth/register/emailExists.js b/frontend/templates/auth/register/emailExists.js new file mode 100644 index 00000000..f1b079c2 --- /dev/null +++ b/frontend/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/frontend/templates/signup/index.html b/frontend/templates/auth/register/index.html similarity index 93% rename from frontend/templates/signup/index.html rename to frontend/templates/auth/register/index.html index 238565b0..617cae0d 100644 --- a/frontend/templates/signup/index.html +++ b/frontend/templates/auth/register/index.html @@ -1,4 +1,4 @@ -<. include!("../components/headers.html"); .> +<. include!("../../components/headers.html"); .>

Join mCaptcha

@@ -62,4 +62,4 @@

-<. include!("../components/footers.html"); .> +<. include!("../../components/footers.html"); .> diff --git a/frontend/templates/auth/register/index.js b/frontend/templates/auth/register/index.js new file mode 100644 index 00000000..8f21aecc --- /dev/null +++ b/frontend/templates/auth/register/index.js @@ -0,0 +1,55 @@ +import ROUTES from '../../api/v1/routes'; + +import isBlankString from '../../utils/genJsonPayload'; +import genJsonPayload from '../../utils/genJsonPayload'; + +import {checkUsernameExists} from './userExists'; +import {checkEmailExists} from './emailExists'; + +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}`); + } +}; + +let form = document.getElementById('form'); +form.addEventListener('submit', registerUser, true); + +let username = document.getElementById('username'); +username.addEventListener('input', checkUsernameEventHandler, false); + +export default registerUser; diff --git a/frontend/templates/auth/register/userExists.js b/frontend/templates/auth/register/userExists.js new file mode 100644 index 00000000..ba05b7fa --- /dev/null +++ b/frontend/templates/auth/register/userExists.js @@ -0,0 +1,48 @@ +import ROUTES from '../../api/v1/routes'; + +import genJsonPayload from '../../utils/genJsonPayload'; + + +const checkUsernameEventHandler = _e => { + checkUsernameExists(); +}; + +//export const checkUsernameExists = async () => { +async function checkUsernameExists() { + 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 {checkUsernameExists, checkUsernameEventHandler}; diff --git a/frontend/templates/index.js b/frontend/templates/index.js new file mode 100644 index 00000000..bd23d3ac --- /dev/null +++ b/frontend/templates/index.js @@ -0,0 +1,21 @@ +import './auth/forms.scss'; + +import signin from './auth/login'; +import registerUser from './auth/register'; +import {run as runPanel} from './panel/index'; +import {checkUsernameEventHandler} from './auth/register/userExists'; + +if (window.location.pathname == '/') { + let form = document.getElementById('form'); + form.addEventListener('submit', signin, true); +} else if (window.location.pathname == '/signup') { + let form = document.getElementById('form'); + form.addEventListener('submit', registerUser, true); + let username = document.getElementById('username'); + username.addEventListener('input', checkUsernameEventHandler, false); +} else if (window.location.pathname.includes('panel')) { + runPanel(); +} else { +} + +//export default signin; diff --git a/frontend/templates/panel/index.js b/frontend/templates/panel/index.js new file mode 100644 index 00000000..62a45420 --- /dev/null +++ b/frontend/templates/panel/index.js @@ -0,0 +1,67 @@ +export const run = () => { + const html = document.documentElement; + const body = document.body; + const menuLinks = document.querySelectorAll('.admin-menu a'); + const collapseBtn = document.querySelector('.admin-menu .collapse-btn'); + const toggleMobileMenu = document.querySelector('.toggle-mob-menu'); + const switchInput = document.querySelector('.switch input'); + const switchLabel = document.querySelector('.switch label'); + const switchLabelText = switchLabel.querySelector('span:last-child'); + const collapsedClass = 'collapsed'; + const lightModeClass = 'light-mode'; + + /*TOGGLE HEADER STATE*/ + collapseBtn.addEventListener('click', function() { + body.classList.toggle(collapsedClass); + this.getAttribute('aria-expanded') == 'true' + ? this.setAttribute('aria-expanded', 'false') + : this.setAttribute('aria-expanded', 'true'); + this.getAttribute('aria-label') == 'collapse menu' + ? this.setAttribute('aria-label', 'expand menu') + : this.setAttribute('aria-label', 'collapse menu'); + }); + + /*TOGGLE MOBILE MENU*/ + toggleMobileMenu.addEventListener('click', function() { + body.classList.toggle('mob-menu-opened'); + this.getAttribute('aria-expanded') == 'true' + ? this.setAttribute('aria-expanded', 'false') + : this.setAttribute('aria-expanded', 'true'); + this.getAttribute('aria-label') == 'open menu' + ? this.setAttribute('aria-label', 'close menu') + : this.setAttribute('aria-label', 'open menu'); + }); + + /*SHOW TOOLTIP ON MENU LINK HOVER*/ + for (const link of menuLinks) { + link.addEventListener('mouseenter', function() { + if ( + body.classList.contains(collapsedClass) && + window.matchMedia('(min-width: 768px)').matches + ) { + const tooltip = this.querySelector('span').textContent; + this.setAttribute('title', tooltip); + } else { + this.removeAttribute('title'); + } + }); + } + + /*TOGGLE LIGHT/DARK MODE*/ + if (localStorage.getItem('dark-mode') === 'false') { + html.classList.add(lightModeClass); + switchInput.checked = false; + switchLabelText.textContent = 'Light'; + } + + switchInput.addEventListener('input', function() { + html.classList.toggle(lightModeClass); + if (html.classList.contains(lightModeClass)) { + switchLabelText.textContent = 'Light'; + localStorage.setItem('dark-mode', 'false'); + } else { + switchLabelText.textContent = 'Dark'; + localStorage.setItem('dark-mode', 'true'); + } + }); +}; diff --git a/frontend/templates/panel/main.scss b/frontend/templates/panel/main.scss new file mode 100644 index 00000000..e67163e5 --- /dev/null +++ b/frontend/templates/panel/main.scss @@ -0,0 +1,147 @@ +/* +