mirror of
https://github.com/mCaptcha/mCaptcha.git
synced 2025-03-29 04:38:59 +03:00
frontend restructured
This commit is contained in:
parent
2ce8a46a3f
commit
312d02a41d
20 changed files with 594 additions and 13 deletions
1
frontend/.gitignore
vendored
1
frontend/.gitignore
vendored
|
@ -3,3 +3,4 @@ dist/
|
||||||
/target
|
/target
|
||||||
.html
|
.html
|
||||||
output/
|
output/
|
||||||
|
static
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
"start": "find . | entr yarn build-dev",
|
"start": "find . | entr yarn build-dev",
|
||||||
"build-dev": "cargo run && webpack --config webpack.dev.js",
|
"build-dev": "cargo run && webpack --config webpack.dev.js",
|
||||||
"build-fast-dev": "cargo run && webpack-dev-server --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"
|
"build": "cargo run && webpack --config webpack.prod.js"
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
|
|
|
@ -4,7 +4,7 @@ use tokio::fs;
|
||||||
use tokio::io::{Error, ErrorKind};
|
use tokio::io::{Error, ErrorKind};
|
||||||
|
|
||||||
#[derive(Clone, TemplateOnce)]
|
#[derive(Clone, TemplateOnce)]
|
||||||
#[template(path = "index.html")]
|
#[template(path = "auth/login/index.html")]
|
||||||
struct IndexPage {
|
struct IndexPage {
|
||||||
name: String,
|
name: String,
|
||||||
title: String,
|
title: String,
|
||||||
|
@ -66,7 +66,7 @@ mod signup {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[derive(TemplateOnce, Clone)]
|
#[derive(TemplateOnce, Clone)]
|
||||||
#[template(path = "signup/index.html")]
|
#[template(path = "auth/register/index.html")]
|
||||||
pub struct IndexPage {
|
pub struct IndexPage {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub title: String,
|
pub title: String,
|
||||||
|
@ -83,7 +83,7 @@ mod signup {
|
||||||
|
|
||||||
impl IndexPage {
|
impl IndexPage {
|
||||||
pub async fn run(&self) -> Result<(), Error> {
|
pub async fn run(&self) -> Result<(), Error> {
|
||||||
let dir = root_path("signup");
|
let dir = root_path("register");
|
||||||
let file = rel_path(&dir, "index.html");
|
let file = rel_path(&dir, "index.html");
|
||||||
|
|
||||||
print!("");
|
print!("");
|
||||||
|
|
4
frontend/templates/_reset.scss
Normal file
4
frontend/templates/_reset.scss
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
* {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
47
frontend/templates/api/v1/routes.js
Normal file
47
frontend/templates/api/v1/routes.js
Normal file
|
@ -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;
|
101
frontend/templates/auth/forms.scss
Normal file
101
frontend/templates/auth/forms.scss
Normal file
|
@ -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);
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
<. include!("components/headers.html"); .>
|
<. include!("../../components/headers.html"); .>
|
||||||
<div class="form-container">
|
<div class="form-container">
|
||||||
<img src="../static/img/icon-trans.png" class="form__logo" alt="">
|
<img src="../static/img/icon-trans.png" class="form__logo" alt="">
|
||||||
<h2 class="form__brand">Sign in to mCaptcha</h2>
|
<h2 class="form__brand">Sign in to mCaptcha</h2>
|
||||||
|
@ -23,8 +23,8 @@
|
||||||
<div class="form__secondary-action">
|
<div class="form__secondary-action">
|
||||||
<p class="form__secondary-action__banner">
|
<p class="form__secondary-action__banner">
|
||||||
New to mCaptcha?
|
New to mCaptcha?
|
||||||
<a href="/signup" class="form__secondary-action__link">Create account</a>
|
<a href="/register" class="form__secondary-action__link">Create account</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body></html>
|
<. include!("../../components/footers.html"); .>
|
26
frontend/templates/auth/login/index.js
Normal file
26
frontend/templates/auth/login/index.js
Normal file
|
@ -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;
|
43
frontend/templates/auth/register/emailExists.js
Normal file
43
frontend/templates/auth/register/emailExists.js
Normal file
|
@ -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};
|
|
@ -1,4 +1,4 @@
|
||||||
<. include!("../components/headers.html"); .>
|
<. include!("../../components/headers.html"); .>
|
||||||
<div class="form-container">
|
<div class="form-container">
|
||||||
<img src="/static/img/icon-trans.png" class="form__logo" alt="" />
|
<img src="/static/img/icon-trans.png" class="form__logo" alt="" />
|
||||||
<h2 class="form__brand">Join mCaptcha</h2>
|
<h2 class="form__brand">Join mCaptcha</h2>
|
||||||
|
@ -62,4 +62,4 @@
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<. include!("../components/footers.html"); .>
|
<. include!("../../components/footers.html"); .>
|
55
frontend/templates/auth/register/index.js
Normal file
55
frontend/templates/auth/register/index.js
Normal file
|
@ -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;
|
48
frontend/templates/auth/register/userExists.js
Normal file
48
frontend/templates/auth/register/userExists.js
Normal file
|
@ -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};
|
21
frontend/templates/index.js
Normal file
21
frontend/templates/index.js
Normal file
|
@ -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;
|
67
frontend/templates/panel/index.js
Normal file
67
frontend/templates/panel/index.js
Normal file
|
@ -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');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
147
frontend/templates/panel/main.scss
Normal file
147
frontend/templates/panel/main.scss
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
/*
|
||||||
|
<style>
|
||||||
|
/* RESET RULES
|
||||||
|
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
||||||
|
:root {
|
||||||
|
--page-header-bgColor: #242e42;
|
||||||
|
--page-header-bgColor-hover: #1d2636;
|
||||||
|
--page-header-txtColor: #dde9f8;
|
||||||
|
--page-header-headingColor: #7889a4;
|
||||||
|
--page-header-width: 220px;
|
||||||
|
--page-content-bgColor: #f0f1f6;
|
||||||
|
--page-content-txtColor: #171616;
|
||||||
|
--page-content-blockColor: #fff;
|
||||||
|
--white: #fff;
|
||||||
|
--black: #333;
|
||||||
|
--blue: #00b9eb;
|
||||||
|
--red: #ec1848;
|
||||||
|
--border-radius: 4px;
|
||||||
|
--box-shadow: 0 0 10px -2px rgba(0, 0, 0, 0.075);
|
||||||
|
--switch-bgLightModeColor: #87cefa;
|
||||||
|
--switch-sunColor: gold;
|
||||||
|
--switch-moonColor: #f4f4f4;
|
||||||
|
--switch-bgDarkModeColor: #1f1f27;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$nav-width: 220px;
|
||||||
|
$nav-txt-color: #dde9f8;
|
||||||
|
$nav-bg-color: #242e42;
|
||||||
|
|
||||||
|
$page-content-blockColor: #fff;
|
||||||
|
$border-radius: 4px;
|
||||||
|
$black: #333;
|
||||||
|
$white: #fff;
|
||||||
|
|
||||||
|
|
||||||
|
/* HEADER STYLES
|
||||||
|
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
||||||
|
.nav-container {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
overflow: auto;
|
||||||
|
padding-top: 20px;
|
||||||
|
width: $nav-width;
|
||||||
|
color: $nav-txt-color;
|
||||||
|
background: $nav-bg-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav__home-btn {
|
||||||
|
display: block;
|
||||||
|
margin: 0 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav__logo {
|
||||||
|
max-width: 120px;
|
||||||
|
fill: $white;
|
||||||
|
filter: invert(100%) sepia(0%) saturate(0%) hue-rotate(298deg)
|
||||||
|
brightness(104%) contrast(101%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav__show-menu {
|
||||||
|
display: none;
|
||||||
|
margin-left: 5px;
|
||||||
|
padding: 4px;
|
||||||
|
background: $page-content-blockColor;
|
||||||
|
border-radius: $border-radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav__show-menu-icon {
|
||||||
|
fill: $black;
|
||||||
|
transition: transform 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-menu {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-grow: 1;
|
||||||
|
margin-top: 35px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header .admin-menu li:nth-last-child(2) {
|
||||||
|
margin-bottom: 35px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header .admin-menu li:last-child {
|
||||||
|
margin-top: auto;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header .admin-menu li > * {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header .admin-menu .switcher {
|
||||||
|
display: inline-block;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header .admin-menu .menu-heading h3 {
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.15em;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-top: 12px;
|
||||||
|
color: var(--page-header-headingColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header .admin-menu svg {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
fill: var(--page-header-txtColor);
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header .admin-menu a,
|
||||||
|
.page-header .admin-menu button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header .admin-menu a:hover,
|
||||||
|
.page-header .admin-menu a:focus,
|
||||||
|
.page-header .admin-menu button:hover,
|
||||||
|
.page-header .admin-menu button:focus {
|
||||||
|
background: var(--page-header-bgColor-hover);
|
||||||
|
color: var(--blue);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header .admin-menu a:hover svg,
|
||||||
|
.page-header .admin-menu a:focus svg,
|
||||||
|
.page-header .admin-menu button:hover svg,
|
||||||
|
.page-header .admin-menu button:focus svg {
|
||||||
|
fill: var(--blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
12
frontend/templates/utils/genJsonPayload.js
Normal file
12
frontend/templates/utils/genJsonPayload.js
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
const genJsonPayload = payload => {
|
||||||
|
let value = {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
};
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default genJsonPayload;
|
8
frontend/templates/utils/isBlankString.js
Normal file
8
frontend/templates/utils/isBlankString.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
const isBlankString = (event, value, field) => {
|
||||||
|
if (!value.replace(/\s/g, '').length) {
|
||||||
|
event.preventDefault();
|
||||||
|
alert(`${field} can't be empty`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default isBlankString;
|
|
@ -2,7 +2,7 @@ const path = require('path');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
entry: {
|
entry: {
|
||||||
main: './static/js/index.js',
|
main: './templates/index.js',
|
||||||
},
|
},
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
|
|
|
@ -14,8 +14,8 @@ module.exports = merge(common, {
|
||||||
template: './output/index.html',
|
template: './output/index.html',
|
||||||
}),
|
}),
|
||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
filename: 'signup/index.html',
|
filename: 'register/index.html',
|
||||||
template: './output/signup/index.html',
|
template: './output/register/index.html',
|
||||||
}),
|
}),
|
||||||
|
|
||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
|
|
|
@ -26,8 +26,8 @@ module.exports = merge(common, {
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
filename: 'signup/index.html', // output filename
|
filename: 'register/index.html', // output filename
|
||||||
template: path.resolve(__dirname, 'output/signup/', 'index.html'),
|
template: path.resolve(__dirname, 'output/register/', 'index.html'),
|
||||||
minify: {
|
minify: {
|
||||||
removeAttributeQuotes: true,
|
removeAttributeQuotes: true,
|
||||||
collapseWhitespace: true,
|
collapseWhitespace: true,
|
||||||
|
|
Loading…
Add table
Reference in a new issue