list sitekey: copy sitekey

This commit is contained in:
realaravinth
2021-07-15 18:07:12 +05:30
parent 97db774e70
commit 863d22f62c
25 changed files with 369 additions and 224 deletions

View File

@@ -17,7 +17,7 @@
@mixin violet-button-hover {
background-color: $light-violet;
cursor: grab;
cursor: pointer;
transform: translateY(-5px);
}

View File

@@ -45,7 +45,7 @@ $message-bg: #d63f3f;
}
.err__close:hover {
cursor: grab;
cursor: pointer;
width: 20px;
height: 20px;
}

View File

@@ -0,0 +1,33 @@
/*
* 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 '../../vars';
@mixin table {
background-color: $white;
width: 90%;
padding: 0 10px;
}
@mixin table__title-text {
font-size: 1rem;
padding: 0.75rem 0.5rem;
box-sizing: border-box;
text-align: left;
width: 100%;
border-bottom: 0.1px solid $light-grey;
}

View File

@@ -1,6 +1,6 @@
.button:hover {
background-color: #993299;
cursor: grab;
cursor: pointer;
transform: translateY(-5px);
}

View File

@@ -21,6 +21,7 @@ import * as login from './auth/login/ts/';
import * as register from './auth/register/ts/';
import * as panel from './panel/ts/index';
import * as addSiteKey from './panel/sitekey/add/ts';
import * as listSitekeys from './panel/sitekey/list/ts';
import {MODE} from './logger';
import log from './logger';
@@ -48,6 +49,7 @@ const router = new Router();
router.register(VIEWS.panelHome, panel.index);
router.register(VIEWS.registerUser, register.index);
router.register(VIEWS.loginUser, login.index);
router.register(VIEWS.listSitekey, listSitekeys.index);
router.register(VIEWS.addSiteKey, addSiteKey.index);
try {

View File

@@ -23,3 +23,4 @@ import './panel/header/taskbar/mobile.scss';
import './panel/navbar/mobile.scss';
import './panel/help-banner/mobile.scss';
import './panel/sitekey/add/css/mobile.scss';
import './panel/sitekey/list/css/mobile.scss';

View File

@@ -4,29 +4,31 @@
-->
<li class="taskbar__spacer"></li>
<li class="taskbar__action">
<a class="taskbar__link" href="<.= crate::PAGES.panel.sitekey.add .>">
<button class="taskbar__add-site">
+ New Site
</button>
<a class="taskbar__link" href="<.= crate::PAGES.panel.sitekey.add .>">
<button class="taskbar__add-site">
+ New Site
</button>
</a>
</li>
<li class="taskbar__action">
<img class="taskbar__icon" src="<.=
crate::FILES.get("./static/cache/img/svg/moon.svg").unwrap() .>" alt="Profile" />
crate::FILES.get("./static/cache/img/svg/moon.svg").unwrap() .>"
alt="Profile" />
</li>
<li class="taskbar__action">
<a href="<.= crate::PAGES.panel.notifications .>">
<img class="taskbar__icon" src="<.=
crate::FILES.get("./static/cache/img/svg/bell.svg").unwrap() .>"
alt="Notifications" />
</a>
<a href="<.= crate::PAGES.panel.notifications .>">
<img class="taskbar__icon" src="<.=
crate::FILES.get("./static/cache/img/svg/bell.svg").unwrap() .>"
alt="Notifications" />
</a>
</li>
<li class="taskbar__action">
<a href="<.= crate::V1_API_ROUTES.auth.logout .>">
<img class="taskbar__icon" src="<.=
crate::FILES.get("./static/cache/img/svg/log-out.svg").unwrap() .>" alt="Profile"
/></a>
<a href="<.= crate::V1_API_ROUTES.auth.logout .>">
<img class="taskbar__icon" src="<.=
crate::FILES.get("./static/cache/img/svg/log-out.svg").unwrap() .>"
alt="Profile" /></a
>
</li>
</ul>

View File

@@ -48,10 +48,9 @@
}
.taskbar__icon:hover {
cursor: grab;
cursor: pointer;
background-color: $light-grey;
color: $green;
background-color: $light-grey;
filter: invert(58%) sepia(60%) saturate(331%) hue-rotate(76deg)
brightness(91%) contrast(92%);
}

View File

@@ -1,40 +1,63 @@
<. include!("../components/headers/index.html"); .>
<. include!("./navbar/index.html"); .>
<. include!("../components/headers/index.html"); .> <.
include!("./navbar/index.html"); .>
<div class="tmp-layout">
<. include!("./header/index.html"); .>
<main class="panel-main">
<. include!("./help-banner/index.html"); .>
<div class="inner-container">
<. include!("./header/index.html"); .>
<main class="panel-main">
<. include!("./help-banner/index.html"); .>
<div class="inner-container">
<. if sitekeys.is_empty() { .>
<ul class="sitekey-list__box">
<p>
It looks like you don't have any sitekeys. Click
<a href="<.= crate::PAGES.panel.sitekey.add .>">here</a> to add new sitekey.
<ul class="sitekey-list__box">
<p>
It looks like you don't have any sitekeys. Click
<a href="<.= crate::PAGES.panel.sitekey.add .>">here</a> to add new
sitekey.
</p>
</ul>
</ul>
<.} else {.>
<ul class="sitekey-list__box">
<h1 class="sitekey-list__title">Your Sitekeys</h1>
<. for sitekey in sitekeys.iter() { .>
<a href="/sitekey/<.= sitekey.key .>/" class="sitekey-list__item-container">
<li class="sitekey-list__item">
<span class="sitekey-list__name">
<.= sitekey.name .>
</span>
<span class="sitekey-list__key">
<.= sitekey.key .>
</span>
</li>
</a>
<. } .>
</ul>
<table class="sitekey__table">
<thead class="sitekey__table-heading">
<tr>
<th colspan="4" class="sitekey__table-title-text">
Your Sitekeys
</th>
</tr>
</thead>
<tbody class="sitekey__body">
<. for sitekey in sitekeys.iter() { .>
<tr class="sitekey__item">
<td class="sitekey-list__name">
<a
href="/sitekey/<.= sitekey.key .>/"
class="sitekey-list__sitekey-link"
>
<.= sitekey.name .>
</a>
</td>
<td class="sitekey-list__key">
<div class="sitekey__key-container">
<img class="sitekey__copy-icon" src="<.= crate::FILES
.get("./static/cache/img/svg/clipboard.svg") .unwrap() .>"
alt="copy sitekey" data-sitekey="<.= sitekey.key .>" /> <img
class="sitekey__copy-done-icon" src="<.= crate::FILES
.get("./static/cache/img/svg/check.svg") .unwrap() .>"
alt="sitekey copied" data-sitekey="<.= sitekey.key .>" />
<a
class="sitekey__widget-link"
href="<.= crate::WIDGET_ROUTES.verification_widget .>/?sitekey=<.= sitekey.key .>"
>
<.= &sitekey.key[0..5] .>
</a>
</div>
</td>
</tr>
<. } .>
</tbody>
</table>
<.}.>
</div>
<. include!("../components/footers.html"); .>
</div>
<. include!("../components/footers.html"); .>
</main>
</div>

View File

@@ -57,7 +57,7 @@
}
.secondary-menu__heading:hover {
color: $green;
cursor: grab;
cursor: pointer;
}
.secondary-menu__logo {
@@ -99,7 +99,7 @@
.secondary-menu__item-link:hover {
color: $green;
cursor: grab;
cursor: pointer;
filter: invert(58%) sepia(60%) saturate(331%) hue-rotate(76deg)
brightness(91%) contrast(92%);
}

View File

@@ -34,7 +34,7 @@
}
.nav__hamburger-menu:hover {
cursor: grab;
cursor: pointer;
}
.nav__hamburger-menu:hover > span {
@@ -75,5 +75,5 @@
.secondary-menu__brand-name:hover {
color: $light-text;
cursor: grab;
cursor: pointer;
}

View File

@@ -16,20 +16,15 @@
*/
@import '../../vars';
@import '../../components//table/main';
.notification__table {
background-color: $white;
width: 90%;
padding: 0 10px;
@include table;
margin: auto;
}
.notification__title-text {
font-size: 1rem;
padding: 0.75rem 0.5rem;
box-sizing: border-box;
text-align: left;
width: 100%;
border-bottom: 0.1px solid $light-grey;
@include table__title-text;
}
.notification__mark-read-btn {
@@ -45,7 +40,7 @@
}
.notification-data__container {
font-size: 0.7rem;
font-size: 0.7rem;
}
.notification__mark-read {

View File

@@ -56,7 +56,7 @@ const submit = async (e: Event) => {
const res = await fetch(formUrl, genJsonPayload(payload));
if (res.ok) {
const data = await res.json();
window.location.assign(VIEWS.listSitekey(data.key));
window.location.assign(VIEWS.viewSitekey(data.key));
} else {
const err = await res.json();
createError(err.error);

View File

@@ -18,38 +18,87 @@
@import '../../../../reset';
@import '../../../../vars';
@import '../../../../components/box';
@import '../../../../components/table/main';
.sitekey-list__box {
@include box;
padding-bottom: 0px;
.sitekey__table {
@include table;
margin: auto;
}
.sitekey-list__title {
@include box-title;
}
.sitekey-list__item-container {
display: block;
width: 100%;
box-sizing: border-box;
border-bottom: 0.1px solid $light-grey;
padding: 20px;
color: $black-text;
}
.sitekey-list__item {
display: flex;
width: 100%;
}
.sitekey-list__item-container:hover {
background-color: $light-grey;
.sitekey__table-title-text {
@include table__title-text;
}
.sitekey-list__name {
flex: 3;
min-width: 450px;
}
.sitekey-list__key {
flex: 1;
width: 10px;
}
@mixin copy-icon-base {
margin: auto;
padding: 5px;
}
.sitekey__copy-icon {
@include copy-icon-base;
}
.sitekey__copy-icon:hover {
cursor: pointer;
filter: invert(17%) sepia(93%) saturate(5039%) hue-rotate(204deg)
brightness(100%) contrast(98%);
}
.sitekey__copy-done-icon {
@include copy-icon-base;
display: none;
filter: invert(58%) sepia(60%) saturate(331%) hue-rotate(76deg)
brightness(91%) contrast(92%);
}
.sitekey__key-container {
border-radius: 10px;
background: $backdrop;
margin: 2px;
padding: 5px;
display: flex;
max-width: 150px;
border: 0.1px solid rgba(0, 0, 0, 0.125);
}
.sitekey__widget-link {
border-left: 0.1px solid $light-grey;
margin: 5;
margin: auto;
padding-left: 20px;
height: 100%;
padding-right: 15px;
}
.sitekey-list__sitekey-link {
display: inline-block;
width: 100%;
text-decoration: none;
color: $blue-link;
padding: 20px;
}
.sitekey-list__sitekey-link:visited {
color: $blue-link;
}
.sitekey-list__sitekey-link:hover {
background-color: $light-grey;
cursor: pointer;
}
.sitekey__widget-link {
color: $blue-link;
}
.sitekey__widget-link:visited {
color: $blue-link;
}

View File

@@ -0,0 +1,20 @@
/*
* 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/>.
*/
.sitekey-list__name {
min-width: 120px;
}

View File

@@ -1,23 +1,53 @@
<. include!("../../../components/headers/index.html"); .>
<. include!("../../navbar/index.html"); .>
<. include!("../../../components/headers/index.html"); .> <.
include!("../../navbar/index.html"); .>
<div class="tmp-layout">
<. include!("../../header/index.html"); .>
<main class="panel-main">
<.include!("../../help-banner/index.html"); .>
<!-- Main content container -->
<div class="inner-container">
<!-- Main menu/ important actions roaster -->
<ul class="sitekey-list__box">
<h1 class="sitekey-list__title">Your Sitekeys</h1>
<. for sitekey in sitekeys.iter() { .>
<a href="/sitekey/<.= sitekey.key .>/" class="sitekey-list__item-container">
<li class="sitekey-list__item">
<span class="sitekey-list__name"><.= sitekey.name .></span>
<span class="sitekey-list__key"><.= sitekey.key .></span>
</li>
</a>
<. } .>
</ul>
</div>
<!-- end of container -->
<. include!("../../../components/footers.html"); .>
<. include!("../../header/index.html"); .>
<main class="panel-main">
<.include!("../../help-banner/index.html"); .>
<!-- Main content container -->
<div class="inner-container">
<!-- Main menu/ important actions roaster -->
<table class="sitekey__table">
<thead class="sitekey__table-heading">
<tr>
<th colspan="4" class="sitekey__table-title-text">
Your Sitekeys
</th>
</tr>
</thead>
<tbody class="sitekey__body">
<. for sitekey in sitekeys.iter() { .>
<tr class="sitekey__item">
<td class="sitekey-list__name">
<a
href="/sitekey/<.= sitekey.key .>/"
class="sitekey-list__sitekey-link"
>
<.= sitekey.name .>
</a>
</td>
<td class="sitekey-list__key">
<div class="sitekey__key-container">
<img class="sitekey__copy-icon" src="<.= crate::FILES
.get("./static/cache/img/svg/clipboard.svg") .unwrap() .>"
alt="copy sitekey" data-sitekey="<.= sitekey.key .>" /> <img
class="sitekey__copy-done-icon" src="<.= crate::FILES
.get("./static/cache/img/svg/check.svg") .unwrap() .>"
alt="sitekey copied" data-sitekey="<.= sitekey.key .>" />
<a
class="sitekey__widget-link"
href="<.= crate::WIDGET_ROUTES.verification_widget .>/?sitekey=<.= sitekey.key .>"
>
<.= &sitekey.key[0..5] .>
</a>
</div>
</td>
</tr>
<. } .>
</tbody>
</table>
</div>
<!-- end of container -->
<. include!("../../../components/footers.html"); .>
</main>
</div>

View File

@@ -15,4 +15,39 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export const index = () => {};
export const index = () => {
registerCopySitekey();
};
const SITEKEY_COPY_ICON = `sitekey__copy-icon`;
const SITEKEY_COPY_DONE_ICON = `sitekey__copy-done-icon`;
const registerCopySitekey = () => {
const icons = document.querySelectorAll(`.${SITEKEY_COPY_ICON}`);
icons.forEach(icon => {
icon.addEventListener('click', e => copySitekey(e));
});
};
/*
* Copy sitekey to clipboard
*/
const copySitekey = async (e: Event) => {
const image = <HTMLElement>e.target;
if (!image.classList.contains(SITEKEY_COPY_ICON)) {
throw new Error(
'This method should only be called when sitekey copy button/icon is clicked',
);
}
const copyDoneIcon = <HTMLElement>(
image.parentElement.querySelector(`.${SITEKEY_COPY_DONE_ICON}`)
);
const sitekey = image.dataset.sitekey;
await navigator.clipboard.writeText(sitekey);
image.style.display = 'none';
copyDoneIcon.style.display = 'block';
setTimeout(() => {
copyDoneIcon.style.display = 'none';
image.style.display = 'block';
}, 1200);
};

View File

@@ -8,47 +8,51 @@
<!-- Main content container -->
<div class="inner-container">
<!-- Main menu/ important actions roaster -->
<form class="sitekey-form" action="<.= crate::V1_API_ROUTES.levels.add .>" method="post">
<h1 class="form__title">Sitekey: <.= name .>
<a href="<.= crate::WIDGET_ROUTES.verification_widget .>/?sitekey=<.= key.>"
>Click here to see CAPTCHA widget in action</a>
</h1>
<label class="sitekey-form__label" for="description">
Description
<input
readonly="readonly"
class="sitekey-form__input"
type="text"
name="description"
id="description"
required
<. if !name.trim().is_empty() { .>
value="<.= name .>"
<. } .>
/>
</label>
<label class="sitekey-form__label" for="duration">
Cooldown Duratoin(in seconds)
<input
readonly="readonly"
class="sitekey-form__input"
type="number"
name="duration"
id="duration"
min=0
required
value="<.= duration .>"
/>
</label>
<. for (count, level) in levels.iter().enumerate() { .>
<. include!("./existing-level.html"); .>
<. } .>
</form>
<form class="sitekey-form" action="<.= crate::V1_API_ROUTES.levels.add .>" method="post">
<h1 class="form__title">Sitekey: <.= name .>
<a
target="_blank"
href="<.= crate::WIDGET_ROUTES.verification_widget .>/?sitekey=<.= key.>"
>View widget
<img class="sitekey-form__widget-link"
src="<.= crate::FILES.get("./static/cache/img/svg/external-link.svg").unwrap() .>"
alt="View widget deployment"
/>
</a>
</h1>
<label class="sitekey-form__label" for="description">
Description
<input
readonly="readonly"
class="sitekey-form__input"
type="text"
name="description"
id="description"
required
<. if !name.trim().is_empty() { .>
value="<.= name .>"
<. } .>
/>
</label>
<label class="sitekey-form__label" for="duration">
Cooldown Duratoin(in seconds)
<input
readonly="readonly"
class="sitekey-form__input"
type="number"
name="duration"
id="duration"
min=0
required
value="<.= duration .>"
/>
</label>
<. for (count, level) in levels.iter().enumerate() { .>
<. include!("./existing-level.html"); .>
<. } .>
</form>
</div>
<!-- end of container -->
<. include!("../../../components/footers.html"); .>

View File

@@ -15,72 +15,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import * as listSitekeys from '../sitekey/list/ts/';
export const index = () => {
// 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');
// }
// });
//
// let a;
listSitekeys.index();
};

View File

@@ -21,8 +21,9 @@ const ROUTES = {
signoutUser: '/api/v1/signout',
panelHome: '/',
docsHome: '/docs/',
listSitekey: (key: string) => `/sitekey/${key}/`,
addSiteKey: '/sitekey/add',
listSitekey: '/sitekeys/',
viewSitekey: (key: string) => `/sitekey/${key}/`,
addSiteKey: '/sitekeys/add',
};
export default ROUTES;