Przeglądaj źródła

settings page, clipboard component

realaravinth 4 lat temu
rodzic
commit
4b18992f6a

+ 1 - 1
Cargo.lock

@@ -1457,7 +1457,7 @@ checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790"
 [[package]]
 [[package]]
 name = "libmcaptcha"
 name = "libmcaptcha"
 version = "0.1.4"
 version = "0.1.4"
-source = "git+https://github.com/mCaptcha/libmcaptcha?branch=master#6eac51de1035c20954752642b4fbdbf6ae3bec6d"
+source = "git+https://github.com/mCaptcha/libmcaptcha?branch=master#1d4f97f511ad851a58359ecd1737b381b61cf22e"
 dependencies = [
 dependencies = [
  "actix",
  "actix",
  "derive_builder",
  "derive_builder",

+ 26 - 0
sqlx-data.json

@@ -14,6 +14,32 @@
       "nullable": []
       "nullable": []
     }
     }
   },
   },
+  "06699fda6b1542bf4544c0bdece91531a3020c24c9c76bcf967980e71ee25b42": {
+    "query": "SELECT email, secret  FROM mcaptcha_users WHERE name = ($1)",
+    "describe": {
+      "columns": [
+        {
+          "ordinal": 0,
+          "name": "email",
+          "type_info": "Varchar"
+        },
+        {
+          "ordinal": 1,
+          "name": "secret",
+          "type_info": "Varchar"
+        }
+      ],
+      "parameters": {
+        "Left": [
+          "Text"
+        ]
+      },
+      "nullable": [
+        true,
+        false
+      ]
+    }
+  },
   "1be6274d5cc6d16f38285b8a62c9f66e8c3014cd403bc599598e911023bfeedb": {
   "1be6274d5cc6d16f38285b8a62c9f66e8c3014cd403bc599598e911023bfeedb": {
     "query": "INSERT INTO mcaptcha_pow_fetched_stats \n        (config_id) VALUES ((SELECT config_id FROM mcaptcha_config WHERE key = $1))",
     "query": "INSERT INTO mcaptcha_pow_fetched_stats \n        (config_id) VALUES ((SELECT config_id FROM mcaptcha_config WHERE key = $1))",
     "describe": {
     "describe": {

+ 1 - 8
src/api/v1/mcaptcha/levels.rs

@@ -30,7 +30,6 @@ pub mod routes {
 
 
     pub struct Levels {
     pub struct Levels {
         pub add: &'static str,
         pub add: &'static str,
-        pub delete: &'static str,
         pub get: &'static str,
         pub get: &'static str,
         pub update: &'static str,
         pub update: &'static str,
     }
     }
@@ -39,14 +38,8 @@ pub mod routes {
         pub const fn new() -> Levels {
         pub const fn new() -> Levels {
             let add = "/api/v1/mcaptcha/add";
             let add = "/api/v1/mcaptcha/add";
             let update = "/api/v1/mcaptcha/update";
             let update = "/api/v1/mcaptcha/update";
-            let delete = "/api/v1/mcaptcha/delete";
             let get = "/api/v1/mcaptcha/get";
             let get = "/api/v1/mcaptcha/get";
-            Levels {
-                add,
-                delete,
-                get,
-                update,
-            }
+            Levels { add, get, update }
         }
         }
     }
     }
 }
 }

+ 8 - 4
src/pages/mod.rs

@@ -11,8 +11,7 @@
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 * 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/>.
+* 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_web::web::ServiceConfig;
 use actix_web::web::ServiceConfig;
@@ -51,17 +50,22 @@ mod tests {
             delete_user(NAME, &data).await;
             delete_user(NAME, &data).await;
         }
         }
 
 
-        let (data, _, signin_resp) = register_and_signin(NAME, EMAIL, PASSWORD).await;
+        register_and_signin(NAME, EMAIL, PASSWORD).await;
+        let (data, _, signin_resp, token_key) = add_levels_util(NAME, PASSWORD).await;
         let cookies = get_cookie!(signin_resp);
         let cookies = get_cookie!(signin_resp);
 
 
         let app = get_app!(data).await;
         let app = get_app!(data).await;
 
 
+        let edit_sitekey_url = format!("/sitekey/{}/edit", &token_key.key);
+        let delete_sitekey_url = format!("/sitekey/{}/delete", &token_key.key);
         let urls = vec![
         let urls = vec![
             PAGES.home,
             PAGES.home,
             PAGES.panel.sitekey.add,
             PAGES.panel.sitekey.add,
             PAGES.panel.sitekey.list,
             PAGES.panel.sitekey.list,
             PAGES.panel.notifications,
             PAGES.panel.notifications,
-            "/sitekey/test/delete",
+            PAGES.panel.settings,
+            &delete_sitekey_url,
+            &edit_sitekey_url,
         ];
         ];
 
 
         for url in urls.iter() {
         for url in urls.iter() {

+ 19 - 15
src/pages/panel/mod.rs

@@ -1,25 +1,26 @@
 /*
 /*
-* 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/>.
-*/
+ * 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/>.
+ */
 
 
 use actix_identity::Identity;
 use actix_identity::Identity;
 use actix_web::{HttpResponse, Responder};
 use actix_web::{HttpResponse, Responder};
 use sailfish::TemplateOnce;
 use sailfish::TemplateOnce;
 
 
 mod notifications;
 mod notifications;
+mod settings;
 pub mod sitekey;
 pub mod sitekey;
 
 
 use crate::errors::PageResult;
 use crate::errors::PageResult;
@@ -51,6 +52,7 @@ async fn panel(data: AppData, id: Identity) -> PageResult<impl Responder> {
 
 
 pub fn services(cfg: &mut actix_web::web::ServiceConfig) {
 pub fn services(cfg: &mut actix_web::web::ServiceConfig) {
     cfg.service(panel);
     cfg.service(panel);
+    cfg.service(settings::settings);
     sitekey::services(cfg);
     sitekey::services(cfg);
     cfg.service(notifications::notifications);
     cfg.service(notifications::notifications);
 }
 }
@@ -61,6 +63,7 @@ pub mod routes {
         pub home: &'static str,
         pub home: &'static str,
         pub sitekey: Sitekey,
         pub sitekey: Sitekey,
         pub notifications: &'static str,
         pub notifications: &'static str,
+        pub settings: &'static str,
     }
     }
 
 
     impl Panel {
     impl Panel {
@@ -69,6 +72,7 @@ pub mod routes {
                 home: "/",
                 home: "/",
                 sitekey: Sitekey::new(),
                 sitekey: Sitekey::new(),
                 notifications: "/notifications",
                 notifications: "/notifications",
+                settings: "/settings",
             }
             }
         }
         }
     }
     }

+ 49 - 0
src/pages/panel/settings.rs

@@ -0,0 +1,49 @@
+/*
+ * 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/>.
+ */
+use actix_identity::Identity;
+use actix_web::{HttpResponse, Responder};
+use sailfish::TemplateOnce;
+
+use crate::errors::PageResult;
+use crate::AppData;
+
+#[derive(TemplateOnce, Clone)]
+#[template(path = "panel/settings/index.html")]
+pub struct IndexPage {
+    email: Option<String>,
+    secret: String,
+}
+
+const PAGE: &str = "Settings";
+
+#[my_codegen::get(path = "crate::PAGES.panel.settings", wrap = "crate::CheckLogin")]
+pub async fn settings(data: AppData, id: Identity) -> PageResult<impl Responder> {
+    let username = id.identity().unwrap();
+
+    let details = sqlx::query_as!(
+        IndexPage,
+        r#"SELECT email, secret  FROM mcaptcha_users WHERE name = ($1)"#,
+        &username,
+    )
+    .fetch_one(&data.db)
+    .await?;
+
+    let body = details.render_once().unwrap();
+    Ok(HttpResponse::Ok()
+        .content_type("text/html; charset=utf-8")
+        .body(body))
+}

+ 1 - 0
static/cache/img/svg/refresh.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-refresh-ccw"><polyline points="1 4 1 10 7 10"></polyline><polyline points="23 20 23 14 17 14"></polyline><path d="M20.49 9A9 9 0 0 0 5.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 0 1 3.51 15"></path></svg>

+ 1 - 1
templates/auth/login/index.html

@@ -34,7 +34,7 @@
         id="password"
         id="password"
         required
         required
       />
       />
-		  <. include!("../../components/showPassword/index.html"); .>
+      <. include!("../../components/showPassword/index.html"); .>
 	</label>
 	</label>
 
 
     <input type="submit" class="sitekey-form__submit" value="Sign in" />
     <input type="submit" class="sitekey-form__submit" value="Sign in" />

+ 38 - 0
templates/components/clipboard/_copy.scss

@@ -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 copy-icon-base {
+  margin: auto;
+  padding: 5px;
+}
+
+@mixin copy-icon {
+  @include copy-icon-base;
+}
+
+@mixin copy-icon-hover {
+  cursor: pointer;
+  filter: invert(17%) sepia(93%) saturate(5039%) hue-rotate(204deg)
+    brightness(100%) contrast(98%);
+}
+
+@mixin copy-done-icon {
+  @include copy-icon-base;
+  display: none;
+  filter: invert(58%) sepia(60%) saturate(331%) hue-rotate(76deg)
+    brightness(91%) contrast(92%);
+}

+ 10 - 0
templates/components/clipboard/index.html

@@ -0,0 +1,10 @@
+<img class="<.= COPY_CLASS .>"
+  src="<.= crate::FILES.get("./static/cache/img/svg/clipboard.svg").unwrap() .>"
+  alt="<.= COPY_ALT .>"
+  data-<.= clipboard_data.0 .>="<.= clipboard_data.1 .>" 
+/> 
+<img
+  class="<.= DONE_CLASS .>"
+  src="<.= crate::FILES.get("./static/cache/img/svg/check.svg").unwrap() .>"
+  alt="<.= DONE_ALT .>"
+/>

+ 65 - 0
templates/components/clipboard/index.ts

@@ -0,0 +1,65 @@
+/*
+ * 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/>.
+ */
+
+class CopyIcon {
+  copyIconClass: string;
+  copyDoneIconClass: string;
+  writeText: string;
+
+  constructor(
+    writeText: string,
+    copyIconClass: string,
+    copyDoneIconClass: string,
+  ) {
+    this.copyIconClass = copyIconClass;
+    this.copyDoneIconClass = copyDoneIconClass;
+    this.writeText = writeText;
+
+    this.__registerHandlers();
+  }
+
+  __registerHandlers() {
+    const icons = document.querySelectorAll(`.${this.copyIconClass}`);
+    icons.forEach(icon => {
+      icon.addEventListener('click', e => this.copySitekey(e));
+    });
+  }
+
+  /*
+   * Copy secret to clipboard
+   */
+  async copySitekey(e: Event) {
+    const image = <HTMLElement>e.target;
+    if (!image.classList.contains(this.copyIconClass)) {
+      throw new Error(
+        'This method should only be called when sitekey copy button/icon is clicked',
+      );
+    }
+    const copyDoneIcon = <HTMLElement>(
+      image.parentElement.querySelector(`.${this.copyDoneIconClass}`)
+    );
+    await navigator.clipboard.writeText(this.writeText);
+    image.style.display = 'none';
+    copyDoneIcon.style.display = 'block';
+    setTimeout(() => {
+      copyDoneIcon.style.display = 'none';
+      image.style.display = 'block';
+    }, 1200);
+  }
+}
+
+export default CopyIcon;

+ 1 - 1
templates/components/footers.html

@@ -10,7 +10,7 @@
 
 
   <link
   <link
     rel="stylesheet"
     rel="stylesheet"
-    media="screen and (max-width: 961px)"
+    media="screen and (max-width: 1200px)"
     type="text/css"
     type="text/css"
     href="<.= &*crate::MOBILE_CSS .>" 
     href="<.= &*crate::MOBILE_CSS .>" 
   />
   />

+ 1 - 12
templates/components/showPassword/index.ts

@@ -50,8 +50,7 @@ const hidePasswordButtons = () => {
 
 
 // e is click event from show password container
 // e is click event from show password container
 export const showPassword = () => {
 export const showPassword = () => {
-  const form = document.querySelector('form');
-  const inputs = form.querySelectorAll('input');
+  const inputs = document.body.querySelectorAll('input');
 
 
   if (display == 'hidden') {
   if (display == 'hidden') {
     display = 'show';
     display = 'show';
@@ -93,13 +92,3 @@ export const registerShowPassword = () => {
 };
 };
 
 
 export default registerShowPassword;
 export default registerShowPassword;
-
-/*
- * so here's what im going to do:
- * the wrapper will be a check box and the check box will manipulate
- * show password and hide password buttons using CSS.
- *
- * There will also be an event hadler attached that will change field types of
- * parent fields only. Well, sibling maybe. Will have to see document structure
- *
- */

+ 3 - 0
templates/index.ts

@@ -20,6 +20,7 @@ import {Router} from './router';
 import * as login from './auth/login/ts/';
 import * as login from './auth/login/ts/';
 import * as register from './auth/register/ts/';
 import * as register from './auth/register/ts/';
 import * as panel from './panel/ts/index';
 import * as panel from './panel/ts/index';
+import settings from './panel/settings/';
 import * as addSiteKey from './panel/sitekey/add/ts';
 import * as addSiteKey from './panel/sitekey/add/ts';
 import * as editSitekey from './panel/sitekey/edit/';
 import * as editSitekey from './panel/sitekey/edit/';
 import * as deleteSitekey from './panel/sitekey/delete/';
 import * as deleteSitekey from './panel/sitekey/delete/';
@@ -37,6 +38,7 @@ import './components/error/main.scss';
 import './components/showPassword/main.scss';
 import './components/showPassword/main.scss';
 import './panel/css/main.scss';
 import './panel/css/main.scss';
 import './panel/navbar/main.scss';
 import './panel/navbar/main.scss';
+import './panel/settings/main.scss';
 import './panel/notifications/main.scss';
 import './panel/notifications/main.scss';
 import './panel/header/taskbar/main.scss';
 import './panel/header/taskbar/main.scss';
 import './panel/help-banner/main.scss';
 import './panel/help-banner/main.scss';
@@ -50,6 +52,7 @@ log.setMode(MODE.production);
 const router = new Router();
 const router = new Router();
 
 
 router.register(VIEWS.panelHome, panel.index);
 router.register(VIEWS.panelHome, panel.index);
+router.register(VIEWS.settings, settings);
 router.register(VIEWS.registerUser, register.index);
 router.register(VIEWS.registerUser, register.index);
 router.register(VIEWS.loginUser, login.index);
 router.register(VIEWS.loginUser, login.index);
 router.register(VIEWS.notifications, notidications.index);
 router.register(VIEWS.notifications, notidications.index);

+ 7 - 6
templates/panel/index.html

@@ -1,3 +1,8 @@
+<. const DONE_ALT: &str = "sitekey copied"; .>
+<. const DONE_CLASS: &str = "sitekey__copy-done-icon"; .>
+<. const COPY_ALT: &str = "copy sitekey"; .>
+<. const COPY_CLASS: &str = "sitekey__copy-icon"; .>
+
 <. include!("../components/headers/index.html"); .> <.
 <. include!("../components/headers/index.html"); .> <.
 include!("./navbar/index.html"); .>
 include!("./navbar/index.html"); .>
 <div class="tmp-layout">
 <div class="tmp-layout">
@@ -37,12 +42,8 @@ include!("./navbar/index.html"); .>
             </td>
             </td>
             <td class="sitekey-list__key">
             <td class="sitekey-list__key">
               <div class="sitekey__key-container">
               <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 .>" />
+				<. let clipboard_data = ("sitekey", &sitekey.key); .> 
+				<. include!("../components/clipboard/index.html"); .>
                 <a
                 <a
                   class="sitekey__widget-link"
                   class="sitekey__widget-link"
                   href="<.= crate::WIDGET_ROUTES.verification_widget .>/?sitekey=<.= sitekey.key .>"
                   href="<.= crate::WIDGET_ROUTES.verification_widget .>/?sitekey=<.= sitekey.key .>"

+ 1 - 1
templates/panel/navbar/index.html

@@ -35,7 +35,7 @@
     </li>
     </li>
 
 
     <li class="secondary-menu__item">
     <li class="secondary-menu__item">
-      <a class="secondary-menu__item-link" href="">
+      <a class="secondary-menu__item-link" href="<.= crate::PAGES.panel.settings .>">
         <img class="secondary-menu__icon" src="<.= crate::FILES.get("./static/cache/img/svg/settings.svg").unwrap() .>" alt="" />
         <img class="secondary-menu__icon" src="<.= crate::FILES.get("./static/cache/img/svg/settings.svg").unwrap() .>" alt="" />
         <div class="secondary-menu__item-name">
         <div class="secondary-menu__item-name">
           Settings
           Settings

+ 56 - 0
templates/panel/settings/index.html

@@ -0,0 +1,56 @@
+<. const COPY_ALT: &str = "copy secret"; .>
+<. const COPY_CLASS: &str = "settings__secret-copy"; .>
+<. const DONE_ALT: &str = "secret copied"; .>
+<. const DONE_CLASS: &str = "settings__secret-copy-done"; .>
+
+<. let clipboard_data = ("secret", &secret); .> 
+
+<. 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">
+  <div class="sitekey-form" action="<.= crate::V1_API_ROUTES.levels.add .>" method="post">
+	<h1 class="form__title">
+		<.= PAGE .>
+	</h1>
+	<form action="<.= crate::V1_API_ROUTES.account.update_email .>" method="post">
+      <label class="sitekey-form__label" for="description">
+          Email
+        <input
+          class="sitekey-form__input"
+          type="email"
+          name="email"
+          id="email"
+          <. if let Some(email) = email { .>
+              <. if !email.trim().is_empty() { .>
+              value="<.= email .>"
+              <. } .>
+          <. } .>
+        />
+      </label>
+      <button class="sitekey-form__submit" type="submit">Update</button>
+	</form>
+
+    <label class="sitekey-form__label" for="secret">
+		Cooldown Duratoin(in seconds)
+	  <input
+		class="sitekey-form__input"
+		type="password"
+		name="secret-password"
+		id="secret"
+		value="<.= secret .>"
+	  />
+      <. include!("../../components/showPassword/index.html"); .>
+      <. include!("../../components/clipboard/index.html"); .>
+	</label>
+    <a class="settings__delete-account-link" href="<.= crate::PAGES.panel.sitekey.add .>">
+        <button class="settings__delete-account-btn" type="submit">Delete Account</button>
+    </a>
+  </div>
+  </div>
+  <!-- end of container -->
+<. include!("../../components/footers.html"); .>

+ 34 - 0
templates/panel/settings/index.ts

@@ -0,0 +1,34 @@
+/*
+ * 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 registerShowPassword from '../../components/showPassword/';
+import CopyIcon from '../../components/clipboard/';
+
+const SECRET_COPY_ICON = 'settings__secret-copy';
+const SECRET_COPY_DONE_ICON = 'settings__secret-copy-done';
+
+const index = () => {
+  registerShowPassword();
+
+  const secretElement = <HTMLElement>(
+    document.querySelector(`.${SECRET_COPY_ICON}`)
+  );
+  const writeText = secretElement.dataset.secret;
+  new CopyIcon(writeText, SECRET_COPY_ICON, SECRET_COPY_DONE_ICON);
+};
+
+export default index;

+ 30 - 0
templates/panel/settings/main.scss

@@ -0,0 +1,30 @@
+/*
+ * 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/clipboard/copy';
+
+.settings__secret-copy {
+  @include copy-icon;
+}
+
+.settings__secret-copy:hover .settings__secret-copy-done:hover {
+  @include copy-icon-hover;
+}
+
+.settings__secret-copy-done {
+  @include copy-done-icon;
+}

+ 6 - 14
templates/panel/sitekey/list/css/main.scss

@@ -18,6 +18,7 @@
 @import '../../../../reset';
 @import '../../../../reset';
 @import '../../../../vars';
 @import '../../../../vars';
 @import '../../../../components/box';
 @import '../../../../components/box';
+@import '../../../../components/clipboard/copy';
 @import '../../../../components/table/main';
 @import '../../../../components/table/main';
 
 
 .sitekey__table {
 .sitekey__table {
@@ -37,26 +38,17 @@
   width: 10px;
   width: 10px;
 }
 }
 
 
-@mixin copy-icon-base {
-  margin: auto;
-  padding: 5px;
-}
-
 .sitekey__copy-icon {
 .sitekey__copy-icon {
-  @include copy-icon-base;
+  @include copy-icon;
 }
 }
 
 
-.sitekey__copy-icon:hover {
-  cursor: pointer;
-  filter: invert(17%) sepia(93%) saturate(5039%) hue-rotate(204deg)
-    brightness(100%) contrast(98%);
+.sitekey__copy-icon:hover,
+.sitekey__copy-done-icon:hover {
+  @include copy-icon-hover;
 }
 }
 
 
 .sitekey__copy-done-icon {
 .sitekey__copy-done-icon {
-  @include copy-icon-base;
-  display: none;
-  filter: invert(58%) sepia(60%) saturate(331%) hue-rotate(76deg)
-    brightness(91%) contrast(92%);
+  @include copy-done-icon;
 }
 }
 
 
 .sitekey__key-container {
 .sitekey__key-container {

+ 11 - 10
templates/panel/sitekey/list/index.html

@@ -1,3 +1,8 @@
+<. const DONE_ALT: &str = "sitekey copied"; .>
+<. const DONE_CLASS: &str = "sitekey__copy-done-icon"; .>
+<. const COPY_ALT: &str = "copy sitekey"; .>
+<. const COPY_CLASS: &str = "sitekey__copy-icon"; .>
+
 <. include!("../../../components/headers/index.html"); .> <.
 <. include!("../../../components/headers/index.html"); .> <.
 include!("../../navbar/index.html"); .>
 include!("../../navbar/index.html"); .>
 <div class="tmp-layout">
 <div class="tmp-layout">
@@ -28,12 +33,8 @@ include!("../../navbar/index.html"); .>
             </td>
             </td>
             <td class="sitekey-list__key">
             <td class="sitekey-list__key">
               <div class="sitekey__key-container">
               <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 .>" />
+				<. let clipboard_data = ("sitekey", &sitekey.key); .> 
+				<. include!("../../../components/clipboard/index.html"); .>
                 <a
                 <a
                   class="sitekey__widget-link"
                   class="sitekey__widget-link"
                   href="<.= crate::WIDGET_ROUTES.verification_widget .>/?sitekey=<.= sitekey.key .>"
                   href="<.= crate::WIDGET_ROUTES.verification_widget .>/?sitekey=<.= sitekey.key .>"
@@ -43,10 +44,10 @@ include!("../../navbar/index.html"); .>
               </div>
               </div>
             </td>
             </td>
             <td class="sitekey-list__key">
             <td class="sitekey-list__key">
-				<div class="sitekey-list__edit">
-                    <. let key = format!("/sitekey/{}", &sitekey.key); .>
-					<. include!("../view/__edit-sitekey-icon.html"); .>
-				</div>
+              <div class="sitekey-list__edit">
+                <. let key = format!("/sitekey/{}", &sitekey.key); .>
+                <. include!("../view/__edit-sitekey-icon.html"); .>
+              </div>
             </td>
             </td>
           </tr>
           </tr>
           <. } .>
           <. } .>

+ 5 - 26
templates/panel/sitekey/list/ts/index.ts

@@ -14,40 +14,19 @@
  * You should have received a copy of the GNU Affero General Public License
  * 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/>.
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
  */
-
-export const index = () => {
-  registerCopySitekey();
-};
+import CopyIcon from '../../../../components/clipboard/';
 
 
 const SITEKEY_COPY_ICON = `sitekey__copy-icon`;
 const SITEKEY_COPY_ICON = `sitekey__copy-icon`;
 const SITEKEY_COPY_DONE_ICON = `sitekey__copy-done-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;
+export const index = () => {
+  const image = <HTMLElement>document.querySelector(`.${SITEKEY_COPY_ICON}`);
   if (!image.classList.contains(SITEKEY_COPY_ICON)) {
   if (!image.classList.contains(SITEKEY_COPY_ICON)) {
     throw new Error(
     throw new Error(
       'This method should only be called when sitekey copy button/icon is clicked',
       '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;
   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);
+
+  new CopyIcon(sitekey, SITEKEY_COPY_ICON, SITEKEY_COPY_DONE_ICON);
 };
 };

+ 1 - 0
templates/views/v1/routes.ts

@@ -20,6 +20,7 @@ const ROUTES = {
   loginUser: '/login/',
   loginUser: '/login/',
   signoutUser: '/api/v1/signout',
   signoutUser: '/api/v1/signout',
   panelHome: '/',
   panelHome: '/',
+  settings: '/settings/',
   docsHome: '/docs/',
   docsHome: '/docs/',
   notifications: '/notifications',
   notifications: '/notifications',
   listSitekey: '/sitekeys/',
   listSitekey: '/sitekeys/',