templates

This commit is contained in:
Aravinth Manivannan 2021-10-13 14:11:39 +05:30
parent 33a7a1882c
commit bc63926d18
Signed by: realaravinth
GPG key ID: AD9F0F08E855ED88
42 changed files with 1224 additions and 90 deletions

View file

@ -30,9 +30,9 @@ frontend: ## Build frontend assets
@yarn install
@-rm -rf ./static/cache/bundle/
@-mkdir ./static/cache/bundle/css/
@yarn sass
@yarn build
@./scripts/bundle.sh
#@yarn run dart-sass -s compressed templates/main.scss ./static/cache/bundle/css/main.css
lint: ## Lint codebase
cargo fmt -v --all -- --emit files

View file

@ -4,7 +4,7 @@
"version": "1.0.0",
"scripts": {
"build": "webpack --mode production",
"sass": "yarn run dart-sass",
"sass": "dart-sass -s compressed templates/main.scss ./static/cache/bundle/css/main.css",
"start": "webpack-dev-server --mode development --progress --color",
"test": "jest"
},

View file

@ -29,7 +29,7 @@ mod api;
mod data;
mod errors;
mod middleware;
//mod pages;
mod pages;
mod settings;
mod static_assets;
#[cfg(test)]
@ -39,7 +39,7 @@ mod tests;
pub use crate::data::Data;
pub use api::v1::ROUTES as V1_API_ROUTES;
pub use middleware::auth::CheckLogin;
//pub use pages::routes::ROUTES as PAGES;
pub use pages::routes::ROUTES as PAGES;
pub use settings::Settings;
pub use static_assets::static_files::assets;
@ -48,9 +48,9 @@ use static_assets::FileMap;
lazy_static! {
pub static ref SETTINGS: Settings = Settings::new().unwrap();
pub static ref FILES: FileMap = FileMap::new();
//
// pub static ref CSS: &'static str =
// FILES.get("./static/cache/bundle/css/main.css").unwrap();
pub static ref CSS: &'static str =
FILES.get("./static/cache/bundle/css/main.css").unwrap();
pub static ref JS: &'static str =
FILES.get("./static/cache/bundle/bundle.js").unwrap();
@ -157,7 +157,7 @@ pub fn get_identity_service() -> IdentityService<CookieIdentityPolicy> {
}
pub fn services(cfg: &mut actix_web::web::ServiceConfig) {
//pages::services(cfg);
pages::services(cfg);
api::v1::services(cfg);
static_assets::services(cfg);
}

View file

@ -19,7 +19,7 @@ use actix_web::{error::ResponseError, http::header, web, HttpResponse, Responder
use lazy_static::lazy_static;
use sailfish::TemplateOnce;
use crate::api::v1::auth::runners;
use crate::api::v1::admin::auth::runners;
use crate::errors::*;
use crate::pages::errors::ErrorPage;
use crate::AppData;
@ -92,10 +92,10 @@ mod tests {
use super::*;
use crate::api::v1::account::{
use crate::api::v1::admin::account::{
username::runners::username_exists, AccountCheckPayload,
};
use crate::api::v1::auth::runners::Register;
use crate::api::v1::admin::auth::runners::Register;
use crate::data::Data;
use crate::tests::*;
use crate::*;

View file

@ -21,7 +21,7 @@ use lazy_static::lazy_static;
use my_codegen::{get, post};
use sailfish::TemplateOnce;
use crate::api::v1::auth::runners;
use crate::api::v1::admin::auth::runners;
use crate::errors::*;
use crate::pages::errors::ErrorPage;
use crate::AppData;
@ -95,7 +95,7 @@ mod tests {
use super::*;
use crate::api::v1::auth::runners::{Login, Register};
use crate::api::v1::admin::auth::runners::{Login, Register};
use crate::data::Data;
use crate::tests::*;
use crate::*;
@ -120,7 +120,7 @@ mod tests {
};
let resp = test::call_service(
&app,
post_request!(&msg, V1_API_ROUTES.auth.register).to_request(),
post_request!(&msg, V1_API_ROUTES.admin.auth.register).to_request(),
)
.await;
assert_eq!(resp.status(), StatusCode::OK);

View file

@ -18,6 +18,8 @@ pub mod join;
pub mod login;
pub mod sudo;
pub use crate::api::v1::admin::get_admin_check_login;
pub fn services(cfg: &mut actix_web::web::ServiceConfig) {
cfg.service(login::login);
cfg.service(login::login_submit);
@ -33,8 +35,8 @@ pub mod routes {
impl Auth {
pub const fn new() -> Auth {
Auth {
login: "/login",
join: "/join",
login: "/api/v1/admin/page/login",
join: "/api/v1/admin/page/join",
}
}

View file

@ -60,11 +60,14 @@ mod tests {
let app = get_app!(data).await;
let urls = vec![
PAGES.home.into(),
//PAGES.home.into(),
PAGES.panel.campaigns.home.into(),
PAGES.panel.campaigns.new.into(),
PAGES.panel.campaigns.get_feedback_route(&campaign.uuid),
PAGES.panel.campaigns.get_delete_route(&campaign.uuid),
// PAGES.panel.campaigns.get_feedback_route(&campaign.uuid),
PAGES
.panel
.campaigns
.get_delete_route(&campaign.campaign_id),
];
for url in urls.iter() {

View file

@ -25,8 +25,9 @@ use my_codegen::{get, post};
use sailfish::TemplateOnce;
use uuid::Uuid;
use crate::api::v1::auth::runners::{login_runner, Login, Password};
use crate::api::v1::campaign::runners;
use super::get_admin_check_login;
use crate::api::v1::admin::auth::runners::{login_runner, Login, Password};
use crate::api::v1::admin::campaigns::runners;
use crate::errors::*;
use crate::pages::auth::sudo::SudoPage;
use crate::AppData;
@ -43,11 +44,11 @@ async fn get_title(
let campaign = sqlx::query_as!(
Name,
"SELECT name
FROM kaizen_campaign
FROM survey_campaigns
WHERE
uuid = $1
id = $1
AND
user_id = (SELECT ID from kaizen_users WHERE name = $2)",
user_id = (SELECT ID from survey_admins WHERE name = $2)",
&uuid,
&username
)
@ -57,7 +58,10 @@ async fn get_title(
Ok(format!("Delete camapign \"{}\"?", campaign.name))
}
#[get(path = "PAGES.panel.campaigns.delete", wrap = "crate::CheckLogin")]
#[get(
path = "PAGES.panel.campaigns.delete",
wrap = "get_admin_check_login()"
)]
pub async fn delete_campaign(
id: Identity,
path: web::Path<String>,
@ -80,7 +84,10 @@ pub async fn delete_campaign(
.body(page))
}
#[post(path = "PAGES.panel.campaigns.delete", wrap = "crate::CheckLogin")]
#[post(
path = "PAGES.panel.campaigns.delete",
wrap = "get_admin_check_login()"
)]
pub async fn delete_campaign_submit(
id: Identity,
path: web::Path<String>,
@ -102,7 +109,7 @@ pub async fn delete_campaign_submit(
let status = e.status_code();
let heading = status.canonical_reason().unwrap_or("Error");
let form_route = crate::V1_API_ROUTES.campaign.get_delete_route(&path);
let form_route = crate::V1_API_ROUTES.admin.campaign.get_delete_route(&path);
let title = get_title(&username, &uuid, &data).await?;
let mut ctx = SudoPage::new(&form_route, &title);
let err = format!("{}", e);
@ -179,7 +186,7 @@ mod tests {
&app,
post_request!(
&creds,
&PAGES.panel.campaigns.get_delete_route(&uuid.uuid),
&PAGES.panel.campaigns.get_delete_route(&uuid.campaign_id),
FORM
)
.cookie(cookies.clone())

View file

@ -14,48 +14,49 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use actix_identity::Identity;
use actix_web::{web, HttpResponse, Responder};
use my_codegen::get;
use sailfish::TemplateOnce;
use crate::api::v1::campaign::{runners, GetFeedbackResp};
use crate::AppData;
use crate::PAGES;
#[derive(TemplateOnce)]
#[template(path = "panel/campaigns/get/index.html")]
struct ViewFeedback<'a> {
campaign: GetFeedbackResp,
uuid: &'a str,
}
const PAGE: &str = "New Campaign";
impl<'a> ViewFeedback<'a> {
pub fn new(campaign: GetFeedbackResp, uuid: &'a str) -> Self {
Self { campaign, uuid }
}
}
#[get(
path = "PAGES.panel.campaigns.get_feedback",
wrap = "crate::CheckLogin"
)]
pub async fn get_feedback(
id: Identity,
data: AppData,
path: web::Path<String>,
) -> impl Responder {
let username = id.identity().unwrap();
let path = path.into_inner();
let feedback_resp = runners::get_feedback(&username, &path, &data)
.await
.unwrap();
let page = ViewFeedback::new(feedback_resp, &path)
.render_once()
.unwrap();
HttpResponse::Ok()
.content_type("text/html; charset=utf-8")
.body(page)
}
//use actix_identity::Identity;
//use actix_web::{web, HttpResponse, Responder};
//use my_codegen::get;
//use sailfish::TemplateOnce;
//
//use crate::api::v1::admin::campaigns::{runners, GetFeedbackResp};
//use crate::AppData;
//use crate::PAGES;
//use super::get_admin_check_login;
//
//#[derive(TemplateOnce)]
//#[template(path = "panel/campaigns/get/index.html")]
//struct ViewFeedback<'a> {
// campaign: GetFeedbackResp,
// uuid: &'a str,
//}
//
//const PAGE: &str = "New Campaign";
//
//impl<'a> ViewFeedback<'a> {
// pub fn new(campaign: GetFeedbackResp, uuid: &'a str) -> Self {
// Self { campaign, uuid }
// }
//}
//
//#[get(
// path = "PAGES.panel.campaigns.get_feedback",
// wrap = "get_admin_check_login()"
//)]
//pub async fn get_feedback(
// id: Identity,
// data: AppData,
// path: web::Path<String>,
//) -> impl Responder {
// let username = id.identity().unwrap();
// let path = path.into_inner();
// let feedback_resp = runners::get_feedback(&username, &path, &data)
// .await
// .unwrap();
// let page = ViewFeedback::new(feedback_resp, &path)
// .render_once()
// .unwrap();
// HttpResponse::Ok()
// .content_type("text/html; charset=utf-8")
// .body(page)
//}

View file

@ -18,10 +18,14 @@ use actix_web::{HttpResponse, Responder};
use my_codegen::get;
use sailfish::TemplateOnce;
use crate::api::v1::campaign::{runners::list_campaign_runner, ListCampaignResp};
use crate::api::v1::admin::campaigns::{
runners::list_campaign_runner, ListCampaignResp,
};
use crate::AppData;
use crate::PAGES;
use super::get_admin_check_login;
pub mod delete;
pub mod get;
pub mod new;
@ -36,10 +40,10 @@ pub mod routes {
impl Campaigns {
pub const fn new() -> Campaigns {
Campaigns {
home: "/campaigns",
new: "/campaigns/new",
get_feedback: "/campaigns/{uuid}/feedback",
delete: "/campaigns/{uuid}/delete",
home: "/api/v1/admin/page/campaigns",
new: "/api/v1/admin/page/campaigns/new",
get_feedback: "/api/v1/admin/page/campaigns/{uuid}/feedback",
delete: "/api/v1/admin/page/campaigns/{uuid}/delete",
}
}
@ -62,7 +66,7 @@ pub fn services(cfg: &mut actix_web::web::ServiceConfig) {
cfg.service(home);
cfg.service(new::new_campaign);
cfg.service(new::new_campaign_submit);
cfg.service(get::get_feedback);
// cfg.service(get::get_feedback);
cfg.service(delete::delete_campaign);
cfg.service(delete::delete_campaign_submit);
}
@ -81,7 +85,7 @@ impl HomePage {
const PAGE: &str = "Campaigns";
#[get(path = "PAGES.panel.campaigns.home", wrap = "crate::CheckLogin")]
#[get(path = "PAGES.panel.campaigns.home", wrap = "get_admin_check_login()")]
pub async fn home(data: AppData, id: Identity) -> impl Responder {
let username = id.identity().unwrap();
let campaigns = list_campaign_runner(&username, &data).await.unwrap();

View file

@ -21,12 +21,14 @@ use lazy_static::lazy_static;
use my_codegen::{get, post};
use sailfish::TemplateOnce;
use crate::api::v1::campaign::{runners, CreateReq};
use crate::api::v1::admin::campaigns::{runners, AddCapmaign};
use crate::errors::*;
use crate::pages::errors::ErrorPage;
use crate::AppData;
use crate::PAGES;
use super::get_admin_check_login;
#[derive(Clone, TemplateOnce)]
#[template(path = "panel/campaigns/new/index.html")]
struct NewCampaign<'a> {
@ -53,20 +55,23 @@ lazy_static! {
static ref INDEX: String = NewCampaign::default().render_once().unwrap();
}
#[get(path = "PAGES.panel.campaigns.new", wrap = "crate::CheckLogin")]
#[get(path = "PAGES.panel.campaigns.new", wrap = "get_admin_check_login()")]
pub async fn new_campaign() -> impl Responder {
HttpResponse::Ok()
.content_type("text/html; charset=utf-8")
.body(&*INDEX)
}
#[post(path = "PAGES.panel.campaigns.new", wrap = "crate::CheckLogin")]
#[post(path = "PAGES.panel.campaigns.new", wrap = "get_admin_check_login()")]
pub async fn new_campaign_submit(
id: Identity,
payload: web::Form<CreateReq>,
payload: web::Json<AddCapmaign>,
data: AppData,
) -> PageResult<impl Responder> {
match runners::new(&payload.into_inner(), &data, &id).await {
let username = id.identity().unwrap();
let mut payload = payload.into_inner();
match runners::add_runner(&username, &mut payload, &data).await {
Ok(_) => {
Ok(HttpResponse::Found()
//TODO show stats of new campaign
@ -113,17 +118,19 @@ mod tests {
let (_, _, signin_resp) = register_and_signin(NAME, EMAIL, PASSWORD).await;
let cookies = get_cookie!(signin_resp);
let new = CreateReq {
let new = AddCapmaign {
name: CAMPAIGN_NAME.into(),
difficulties: DIFFICULTIES.into(),
};
let new_resp = test::call_service(
&app,
post_request!(&new, PAGES.panel.campaigns.new, FORM)
.cookie(cookies.clone())
post_request!(&new, crate::PAGES.panel.campaigns.new)
.cookie(cookies)
.to_request(),
)
.await;
assert_eq!(new_resp.status(), StatusCode::FOUND);
let headers = new_resp.headers();
assert_eq!(

View file

@ -16,6 +16,8 @@
use actix_web::{http, HttpResponse, Responder};
use my_codegen::get;
pub use crate::api::v1::admin::get_admin_check_login;
use crate::PAGES;
mod campaigns;
@ -30,7 +32,7 @@ pub mod routes {
impl Panel {
pub const fn new() -> Panel {
Panel {
home: "/",
home: "/api/v1/admin/home/",
campaigns: Campaigns::new(),
}
}
@ -47,7 +49,7 @@ pub fn services(cfg: &mut actix_web::web::ServiceConfig) {
campaigns::services(cfg);
}
#[get(path = "PAGES.panel.home", wrap = "crate::CheckLogin")]
#[get(path = "PAGES.panel.home", wrap = "get_admin_check_login()")]
pub async fn home() -> impl Responder {
HttpResponse::Found()
.insert_header((http::header::LOCATION, PAGES.panel.campaigns.home))

View file

@ -127,6 +127,7 @@ mod tests {
assets::LOGO.path,
assets::HEADSETS.path,
*crate::JS,
*crate::CSS,
*crate::GLUE,
]
.iter()

View file

@ -0,0 +1,11 @@
<. if crate::SETTINGS.allow_demo && crate::SETTINGS.allow_registration { .>
<p class="auth__demo-user__banner">
Try Kaizen without joining<br />
<span class="auth__demo-user__cred" >
<b>user:</b> <.= crate::demo::DEMO_USER .>
</span>
<span class="auth__demo-user__cred">
<b>password:</b> <.= crate::demo::DEMO_PASSWORD .>
</span>
</p>
<. } .>

View file

@ -0,0 +1,69 @@
<. include!("../../components/base/top.html"); .>
<body class="auth__body">
<main class="auth__container">
<img src="<.= crate::assets::LOGO.path .>" alt="logo" class="auth__logo" />
<h1>Join Kaizen</h1>
<. include!("../../components/error/index.html"); .>
<form
action="<.= crate::PAGES.auth.join .>"
method="POST"
class="form"
accept-charset="utf-8"
>
<label class="form__label" for="username">
Username
<input
class="form__input"
name="username"
required
id="username"
type="text"
/>
</label>
<label class="form__label" for="email">
Email(optional)
<input
class="form__input"
name="email"
id="email"
type="email"
/>
</label>
<label class="form__label" for="password">
password
<input
class="form__input"
name="password"
required
id="password"
type="password"
/>
</label>
<label class="form__label" for="confirm_password">
Re-enter Password
<input
class="form__input"
name="confirm_password"
required
id="confirm_password"
type="password"
/>
</label>
<div class="form__action-container">
<a href="/forgot-password">Forgot password?</a>
<button class="form__submit" type="submit">Login</button>
</div>
</form>
<p class="form__alt-action">
Already have an account?
<a href="<.= crate::PAGES.auth.login .>"> Login </a>
</p>
</main>
<. include!("../../components/footer/index.html"); .>
</body>
<. include!("../../components/base/bottom.html"); .>

View file

@ -0,0 +1,48 @@
<. include!("../../components/base/top.html"); .>
<body class="auth__body">
<main class="auth__container">
<img src="<.= crate::assets::LOGO.path .>" alt="logo" class="auth__logo" />
<h1>Sign in</h1>
<. include!("../../components/error/index.html"); .>
<form
action="<.= crate::PAGES.auth.login .>"
method="POST"
class="form"
accept-charset="utf-8"
>
<label class="form__label" for="login">
Username or Email
<input
class="form__input"
name="login"
required
id="login"
type="text"
/>
</label>
<label class="form__label" for="password">
password
<input
class="form__input"
name="password"
required
id="password"
type="password"
/>
</label>
<div class="form__action-container">
<a href="/forgot-password">Forgot password?</a>
<button class="form__submit" type="submit">Login</button>
</div>
</form>
<p class="form__alt-action">
New to Kaizen?
<a href="<.= crate::PAGES.auth.join .>">Create an account </a>
</p>
</main>
<. include!("../../components/footer/index.html"); .>
</body>
<. include!("../../components/base/bottom.html"); .>

50
templates/auth/main.scss Normal file
View file

@ -0,0 +1,50 @@
/*
* Copyright (C) 2021 Aravinth Manivannan <realaravinth@batsense.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
.auth__body {
height: 100vh;
max-height: 100vh;
overflow-y: hidden;
}
.auth__container {
width: 380px;
align-items: center;
margin: auto;
display: flex;
flex-direction: column;
}
.auth__logo {
width: 150px;
padding-left: 20px;
}
.auth__demo-user__banner {
margin: auto;
margin-top: 5px;
font-size: 0.8rem;
text-align: center;
}
.auth__demo-user__cred {
font-family: monospace, monospace;
}
.sudo__message {
margin-top: 20px;
margin-bottom: 20px;
}

View file

@ -0,0 +1,34 @@
<. include!("../../components/base/top.html"); .>
<body class="auth__body">
<main class="auth__container">
<img src="<.= crate::assets::LOGO.path .>" alt="logo" class="auth__logo" />
<h1>Confirm Access</h1>
<p class="sudo__message"><b><.= title .></b></p>
<. include!("../../components/error/index.html"); .>
<form
action="<.= url .>"
class="form"
method="POST"
accept-charset="utf-8"
>
<label class="form__label" for="password">
password
<input
class="form__input"
name="password"
required
id="password"
type="password"
/>
</label>
<div class="form__action-container">
<a href="/forgot-password">Forgot password?</a>
<button class="form__submit" type="submit">Authorize</button>
</div>
</form>
</main>
<. include!("../../components/footer/index.html"); .>
</body>
<. include!("../../components/base/bottom.html"); .>

View file

@ -0,0 +1,38 @@
/*
* Copyright (C) 2021 Aravinth Manivannan <realaravinth@batsense.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
@mixin button {
border: none;
// margin: 10px 0;
// background-color: none;
background-color: #b4345b;
color: #fff;
// text-align: center;
border-radius: 0px;
padding: 0.1rem 0.75rem;
font-weight: 400;
text-align: center;
line-height: 1.2rem;
white-space: nowrap;
vertical-align: middle;
user-select: none;
border: 1px solid transparent;
}

View file

@ -0,0 +1 @@
</html>

View file

@ -0,0 +1,8 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title><.= PAGE .> | <.= crate::pages::NAME .></title>
<link rel="stylesheet" href="<.= &*crate::CSS .>" />
</head>

View file

@ -0,0 +1,6 @@
<. if let Some(error) = error { .>
<div class="error__container">
<p class="error__title"><b><.= error.title .></b></p>
<p class="error__msg"><.= error.message .></p>
</div>
<. } .>

View file

@ -0,0 +1,36 @@
/*
* Copyright (C) 2021 Aravinth Manivannan <realaravinth@batsense.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
.error__container {
width: 350px;
background-color: #d63f3f;
width: 100%;
padding: 10px 0;
opacity: 0.9;
font-size: 0.8rem;
font-family: monospace, monospace;
}
.error__title {
color: #fff;
margin-left: 10px;
}
.error__msg {
color: #fff;
margin-left: 10px;
}

View file

@ -0,0 +1,24 @@
<footer class="footer">
<. use crate::PAGES; .>
<ul class="footer__section">
<li class="footer__item">
<a class="footer__link" href="<.= PAGES.about .>">About</a>
</li>
<li class="footer__item">
<a class="footer__link" href="<.= PAGES.donate .>">Donate</a>
</li>
<li class="footer__item">
<a class="footer__link" href="<.= PAGES.privacy .>">Privacy</a>
</li>
<li class="footer__item">
<a class="footer__link" href="<.= PAGES.security .>">Security</a>
</li>
<li class="footer__item">
<a class="footer__link" href="<.= PAGES.thanks .>">Thanks</a>
</li>
<li class="footer__item">
<a class="footer__link" href="<.= &*crate::SOURCE_FILES_OF_INSTANCE .>">
v<.= crate::VERSION .>-<.= crate::GIT_COMMIT_HASH[0..8] .>
</li>
</ul>
</footer>

View file

@ -0,0 +1,36 @@
/*
* Copyright (C) 2021 Aravinth Manivannan <realaravinth@batsense.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
.footer {
font-size: 0.8rem;
padding: 0px 20px;
position: relative;
bottom: 10px;
width: 100%;
}
.footer__section {
display: inline;
float: right;
}
.footer__item {
display: inline;
list-style: none;
padding: 10px;
}

View file

@ -0,0 +1,55 @@
/*
* Copyright (C) 2021 Aravinth Manivannan <realaravinth@batsense.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
@import "../button";
@mixin form {
margin: 15px auto;
width: 100%;
}
@mixin label {
display: block;
margin: 10px 0;
}
@mixin input {
display: block;
margin: 10px 0;
width: 100%;
height: 35px;
border-radius: 0px;
border: 1px solid grey;
}
@mixin action_container {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
}
@mixin alt_action {
font-size: 0.9rem;
}
@mixin submit {
@include button;
}
@mixin submit_hover {
cursor: pointer;
}

View file

@ -0,0 +1,45 @@
/*
* Copyright (C) 2021 Aravinth Manivannan <realaravinth@batsense.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
@import "partials";
.form {
@include form;
}
.form__label {
@include label;
}
.form__input {
@include input;
}
.form__action-container {
@include action_container;
}
.form__alt-action {
@include alt_action;
}
.form__submit {
@include submit;
}
.form__submit:hover {
@include submit_hover;
}

View file

View file

@ -0,0 +1,9 @@
<. include!("../components/base/top.html"); .>
<body class="panel__body">
<main class="panel__container">
<h1 class="error-title"><.= title .></h1>
<p class="error-message"><.= message .></p>
</main>
<. include!("../components/footer/index.html"); .>
</body>
<. include!("../components/base/bottom.html"); .>

View file

@ -0,0 +1,23 @@
/*
* Copyright (C) 2021 Aravinth Manivannan <realaravinth@batsense.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
.error-title {
text-align: center;
}
.error-message {
text-align: center;
}

63
templates/index.html Normal file
View file

@ -0,0 +1,63 @@
<. include!("./components/base/top.html"); .>
<body class="panel__body">
<main class="panel__container">
<h1>mCaptcha device benchmark survey</h1>
<h2>Why should I participate</h2>
<p>
<a href="https://mcaptcha.org" target="_blank">mCaptcha</a>
is a cutting edge, privacy respecting CAPTCHA system. We use a
<a href="https://en.wikipedia.org/wiki/Proof_of_work" target="_blank"
>proof of work</a
>
based mechanism to defend against
<a href="https://en.wikipedia.org/wiki/Proof_of_work" target="_blank"
>denial-of-service attacks</a
>
which allows for a non-interactive CAPTCHA experience. We require
performance metrics measured on a wide range of devices to fine-tune the
system for optimal user experience, see for yourself!
</p>
<div style="width: 304px; height: 78px">
<iframe
title="mCaptcha"
src="https://demo.mcaptcha.org/widget/?sitekey=6o3p1Fx94hJRFm8g8IHBB7sv8D0em20k"
role="presentation"
name="mcaptcha-widget__iframe"
id="mcaptcha-widget__iframe"
scrolling="no"
sandbox="allow-same-origin allow-scripts"
width="304"
height="78"
data-mcaptcha_host="https://demo.mcaptcha.org"
frameborder="0"
></iframe>
</div>
<h2>What do I get?</h2>
<p>
We are conducting a lucky draw. Two lucky participants, selected at
random, will win a pair of
<a
target="_blank"
href="https://www.amazon.in/JBL-Detachable-Directional-Headphones-Conference/dp/B0948TG7H8"
>JBL headsets worth over ₹2,500</a
>! Also, you will be helping us make the internet healthier :)
</p>
<h2>Privacy policy</h2>
<p>This survey collects the following information:</p>
<ul>
<li>Device name</li>
<li>Operating system</li>
<li>Processor information</li>
<li>Benchmark results</li>
</ul>
<b>No Personally identifying information is collected</b>
</main>
<. include!("../components/footer/index.html"); .>
<a href="<.= crate::V1_API_ROUTES.benches.register .>" target="_blank"
>Get started</a
>
</body>
<script src="./dist/bundle.js"></script>
<. include!("./components/base/bottom.html"); .>

51
templates/main.scss Normal file
View file

@ -0,0 +1,51 @@
/*
* Copyright (C) 2021 Aravinth Manivannan <realaravinth@batsense.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
@import "./components/footer/main";
@import "./components/form/main";
@import "./components/error/main";
@import "./auth/main";
@import "./panel/main";
@import "./panel/campaigns/new/main";
@import "./panel/campaigns/get/main";
@import "./errors/main";
* {
margin: 0;
padding: 0;
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
font-family: "Inter UI", -apple-system, BlinkMacSystemFont, "Roboto",
"Segoe UI", Helvetica, Arial, sans-serif;
}
a {
text-decoration: none;
color: rgb(0, 86, 179);
}
a:hover {
text-decoration: underline;
}
body {
max-width: 100vw;
overflow-x: hidden;
min-height: 100vh;
display: flex;
flex-direction: column;
}

View file

@ -0,0 +1,58 @@
<. include!("../../../components/base/top.html"); .>
<body class="panel__body">
<. include!("../../nav/index.html"); .>
<main class="panel__container">
<h1><.= campaign.name .></h1>
<a href="<.= crate::PAGES.panel.campaigns.get_delete_route(&uuid) .>" >
<img src="<.= crate::assets::TRASH.path .>" alt="<.= crate::assets::TRASH.name .>" class="feedback__trash-logo" />
</a>
<div class="asset__container">
<span class="asset__name">Campaign ID</span>
<code id="campaign-id" class="asset__value"><.= uuid .></code>
</div>
<. if campaign.feedbacks.is_empty() { .>
<p>
Looks like you don't have any feedback on this campaign.
</p>
<. } else { .>
<table class="feedback__table">
<thead class="feedback__heading">
<tr>
<th class="feedback__title-text--normal">Time</th>
<th class="feedback__title-text--small">Helpful</th>
<th class="feedback__title-text--large">Description</th>
</tr>
</thead>
<tbody class="feedback__body">
<. for feedback in campaign.feedbacks.iter() { .>
<tr class="feedback__item">
<td>
<.= feedback.time .>
</td>
<td>
<.= feedback.helpful .>
</td>
<td class="feedback__description">
<. if feedback.description.len() > 60 { .>
<details>
<. let (summary, rest) = feedback.description.split_at(60); .>
<summary>
<.= summary .>
</summary> >>
<.= rest .>
</details>
<. } else { .>
<.= feedback.description .>
<. } .>
<. } .>
</td>
</tr>
</tbody>
</table>
<. } .>
</main>
<. include!("../../../components/footer/index.html"); .>
</body>
<. include!("../../../components/base/bottom.html"); .>

View file

@ -0,0 +1,37 @@
/*
* Copyright (C) 2021 Aravinth Manivannan <realaravinth@batsense.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
.feedback__table {
width: 80%;
margin: 40px;
}
.feedback__title-text--small {
width: 50px;
}
.feedback__title-text--large {
width: 350px;
text-align: start;
}
.feedback__title-text--normal {
width: 150px;
}
.feedback__description{
text-align: start;
}

View file

@ -0,0 +1,42 @@
<. include!("../../components/base/top.html"); .>
<body class="panel__body">
<. include!("../nav/index.html"); .>
<main class="panel__container">
<. if data.is_empty() { .>
<p>
Looks like you don't have any campaings registered,
<a class="link__btn" href="<.= crate::PAGES.panel.campaigns.new .>"> click here </a>
to create a new campaign!
</p>
<. } else { .>
<table class="campaign__table">
<thead class="campaign__heading">
<tr>
<th class="campaign__title-text">Name</th>
<th class="campaign__title-text">UUID</th>
</tr>
</thead>
<tbody class="campaign__body">
<. for campaign in data.iter() { .>
<. let route = crate::PAGES.panel.campaigns.get_feedback_route(&campaign.uuid); .>
<tr class="campaign__item">
<td>
<a href="<.= &route .>">
<p class="campaign__item-heading"><.= campaign.name .></p>
</a>
</td>
<td>
<a href="<.= &route .>">
<p class="campaign__item-text"><.= campaign.uuid .></p></a
>
</td>
</tr>
<. } .>
</tbody>
</table>
<. } .>
</main>
<. include!("../../components/footer/index.html"); .>
</body>
<. include!("../../components/base/bottom.html"); .>

View file

@ -0,0 +1,21 @@
<h1>Create new campaigns</h1>
<. include!("../../../components/error/index.html"); .>
<form
action="<.= crate::PAGES.panel.campaigns.new .>"
method="POST"
class="new-campaign__form"
accept-charset="utf-8"
>
<label class="form__label" for="name">
Name
<input
class="form__input"
name="name"
placeholder=" Joe's website or https://example.com/docs/"
required
id="name"
type="text"
/>
</label>
<button class="form__submit" type="submit">Create Campaign</button>
</form>

View file

@ -0,0 +1,7 @@
<. include!("../../../components/base/top.html"); .>
<body class="panel__body">
<. include!("../../nav/index.html"); .>
<main class="panel__container"><. include!("./form.html"); .></main>
<. include!("../../../components/footer/index.html"); .>
</body>
<. include!("../../../components/base/bottom.html"); .>

View file

@ -0,0 +1,22 @@
/*
* Copyright (C) 2021 Aravinth Manivannan <realaravinth@batsense.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
@import "../../../components/form/partials";
.new-campaign__form {
@include form;
width: 480px;
}

91
templates/panel/main.scss Normal file
View file

@ -0,0 +1,91 @@
/*
* Copyright (C) 2021 Aravinth Manivannan <realaravinth@batsense.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
@import "./nav/main.scss";
@import "../components/button";
.panel__body {
width: 100vw;
min-height: 100vh;
}
.panel__container {
width: 80%;
align-items: center;
margin: auto;
margin-top: 50px;
display: flex;
flex-direction: column;
}
.link__btn {
@include button;
margin: 0px 5px;
padding: 0.3rem 0.3rem;
padding-bottom: 0.2rem;
}
table {
border-collapse: collapse;
caption-side: bottom;
border-color: #e9ecef;
text-align: center;
min-width: 50%;
margin: 20px auto;
}
table > thead {
vertical-align: bottom;
border-bottom: 1px solid #cdc8ca;
text-align: center;
}
table {
th {
text-align: center;
}
td {
margin: auto;
padding: 10px;
border-bottom: 1px solid #edddd1;
}
}
.asset__container {
margin: 10px auto;
}
.asset__name {
font-weight: bold;
}
.asset__value {
margin: 10px 0;
display: inline;
width: auto;
word-wrap: break-word;
overflow-wrap: break-word;
font-family: monospace, monospace;
background-color: #f2f2f2;
padding: 0.125rem 0.25rem;
margin: 0 0.25rem;
border-radius: 3px;
font-size: 1em;
color: #1a1a1a;
overflow-x: auto;
white-space: pre;
}

View file

@ -0,0 +1,45 @@
<nav class="nav__container">
<input type="checkbox" class="nav__toggle" id="nav__toggle" />
<div class="nav__header">
<a class="nav__logo-container" href="<.= crate::PAGES.home .>">
<img src="<.= crate::assets::LOGO.path .>" alt="<.= crate::assets::LOGO.name .>" class="nav__logo" />
<p class="nav__logo-text">KAIZEN</p>
</a>
<label class="nav__hamburger-menu" for="nav__toggle">
<span class="nav__hamburger-inner"></span>
</label>
</div>
<div class="nav__spacer"></div>
<div class="nav__link-group">
<div class="nav__link-container">
<a class="nav__link" rel="noreferrer" href="<.= crate::PAGES.home .>"
>Home</a
>
</div>
<div class="nav__link-container">
<a
class="nav__link"
rel="noreferrer"
href="<.= crate::PAGES.panel.campaigns.home .>"
>Campaigns</a
>
</div>
<div class="nav__link-container">
<a class="nav__link" rel="noreferrer" href="/settings">Settings</a>
</div>
<div class="nav__link-container">
<a
class="nav__link"
rel="noreferrer"
href="<.= crate::V1_API_ROUTES.admin.auth.logout .>"
>Log out</a
>
</div>
</div>
</nav>

View file

@ -0,0 +1,75 @@
/*
* Copyright (C) 2021 Aravinth Manivannan <realaravinth@batsense.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
.nav__container {
display: flex;
flex-direction: row;
box-sizing: border-box;
width: 100%;
}
.nav__hamburger-menu {
display: none;
}
.nav__spacer {
flex: 3;
margin: auto;
}
.nav__logo-container {
display: inline-flex;
}
.nav__toggle {
display: none;
}
.nav__logo {
display: inline-flex;
margin: auto;
padding: 5px;
width: 40px;
}
.nav__logo-text {
margin: auto;
letter-spacing: 5px;
padding: 5px;
}
.nav__link-group {
list-style: none;
display: flex;
flex-direction: row;
align-items: center;
align-self: center;
margin: auto;
text-align: center;
}
.nav__link-container {
display: flex;
padding: 10px;
height: 100%;
margin: auto;
}
.nav__link {
text-decoration: none;
}

View file

@ -0,0 +1,102 @@
$hamburger-menu-animation: 0.4s ease-out;
$nav__hamburger-inner-height: 1.3px;
.nav__container {
flex-direction: column;
}
.nav__header {
display: flex;
flex-direction: row;
min-width: 100%;
height: 55px;
justify-content: space-between;
}
.nav__link-group {
position: sticky;
flex-direction: column;
}
.nav__hamburger-menu {
display: inline-block;
width: 50px;
height: 50px;
}
.nav__spacer {
display: none;
}
.nav__toggle:not(:checked) ~ .nav__link-group {
max-height: 0;
transition: max-height $hamburger-menu-animation;
overflow: hidden;
}
.nav__toggle:checked ~ .nav__link-group {
max-height: 500px;
transition: max-height $hamburger-menu-animation;
}
.nav__toggle:checked ~ .nav__header {
.nav__hamburger-inner::after {
width: 24px;
bottom: $nav__hamburger-inner-height;
transform: rotate(-90deg);
transition: bottom 0.1s ease-out,
transform 0.22s cubic-bezier(0.215, 0.61, 0.355, 1) 0.12s,
width 0.1s ease-out;
}
.nav__hamburger-inner::before {
top: 0;
opacity: 0;
transition: top 0.1s ease-out, opacity 0.1s ease-out 0.12s;
}
.nav__hamburger-inner {
transform: rotate(225deg);
transition-delay: 0.12s;
transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
}
}
.nav__hamburger-inner::after {
bottom: -7px;
transition: bottom 0.1s ease-in 0.25s,
transform 0.22s cubic-bezier(0.55, 0.055, 0.675, 0.19),
width 0.1s ease-in 0.25s;
}
.nav__hamburger-inner::after,
.nav__hamburger-inner::before {
content: '';
display: block;
}
.nav__hamburger-inner::before {
top: -7px;
transition: top 0.1s ease-in 0.25s, opacity 0.1s ease-in;
}
.nav__hamburger-inner {
top: 50%;
margin: auto;
transition-duration: 0.22s;
transition-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
}
.nav__hamburger-inner,
.nav__hamburger-inner::after,
.nav__hamburger-inner::before {
width: 24px;
height: $nav__hamburger-inner-height;
position: relative;
background: #000;
}
.nav__hamburger-menu,
.nav__hamburger-inner {
display: block;
}