From ad5723b784d806edbc9ee9391f864bdeb0fdee7a Mon Sep 17 00:00:00 2001 From: skydread1 Date: Tue, 25 Jul 2023 14:36:02 +0800 Subject: [PATCH 1/9] Remove admin nav link --- client/web/src/flybot/client/web/core/dom/header.cljs | 2 -- 1 file changed, 2 deletions(-) diff --git a/client/web/src/flybot/client/web/core/dom/header.cljs b/client/web/src/flybot/client/web/core/dom/header.cljs index 783d852e..25b424ac 100644 --- a/client/web/src/flybot/client/web/core/dom/header.cljs +++ b/client/web/src/flybot/client/web/core/dom/header.cljs @@ -1,7 +1,6 @@ (ns flybot.client.web.core.dom.header (:require [flybot.client.web.core.dom.common.link :refer [internal-link]] [flybot.client.web.core.dom.common.svg :as svg] - [flybot.client.web.core.dom.page.admin :as admin] [re-frame.core :as rf])) (defn login-link @@ -19,7 +18,6 @@ (internal-link :flybot/about "About Us") (internal-link :flybot/blog "Blog") (internal-link :flybot/contact "Contact" false) - (when (admin/admin?) (internal-link :flybot/admin "Admin")) (login-link)]) (defn navbar-web [] From aecefe402d5d158a4a3baba5b481ae8b84fb13d4 Mon Sep 17 00:00:00 2001 From: skydread1 Date: Tue, 25 Jul 2023 14:36:31 +0800 Subject: [PATCH 2/9] Only owners can add roles --- .../src/flybot/client/common/db/event.cljs | 40 ++++++++------ .../client/web/core/dom/page/admin.cljs | 54 ++++++++++--------- 2 files changed, 53 insertions(+), 41 deletions(-) diff --git a/client/common/src/flybot/client/common/db/event.cljs b/client/common/src/flybot/client/common/db/event.cljs index 9fe3a31d..620b4620 100644 --- a/client/common/src/flybot/client/common/db/event.cljs +++ b/client/common/src/flybot/client/common/db/event.cljs @@ -56,7 +56,7 @@ (let [post (-> posts :removed-post) user-name (-> db :app/user :user/name)] {:fx [[:dispatch [:evt.post/delete-post post]] - [:dispatch [:evt.post.form/clear-form]] + [:dispatch [:evt.form/clear :form/fields]] [:dispatch [:evt.error/clear-errors]] [:fx.log/message ["Post " (:post/id post) " deleted by " user-name "."]]]}))) @@ -67,15 +67,21 @@ :fx [[:fx.log/message ["User logged out."]]]})) (rf/reg-event-fx - :fx.http/grant-admin-success - (fn [_ [_ {:keys [users]}]] - (let [{:user/keys [name roles]} (-> users :new-role :admin)] - {:fx [[:dispatch [:evt.post.form/clear-form]] + :fx.http/grant-role-success + (fn [_ [_ role-granted {:keys [users]}]] + (let [{:user/keys [name roles]} (-> users :new-role role-granted)] + {:fx [[:dispatch [:evt.form/clear :form.role/fields]] [:dispatch [:evt.error/clear-errors]] [:fx.log/message ["User " name " 's roles are now " (map :role/name roles)]]]}))) ;; ---------- User ---------- +(rf/reg-event-db + :evt.role.form/set-field + [(rf/path :form.role/fields)] + (fn [form [_ role id value]] + (assoc-in form [role id] value))) + (rf/reg-event-db :evt.user/toggle-mode [(rf/path :user/mode)] @@ -92,23 +98,23 @@ :on-failure [:fx.http/failure]}})) (rf/reg-event-fx - :evt.user.form/grant-admin - (fn [{:keys [db]} _] - (let [new-admin-email (-> db :form/fields :new-admin/email (valid/validate valid/user-email-schema))] - (if (:errors new-admin-email) - {:fx [[:dispatch [:evt.error/set-validation-errors (valid/error-msg new-admin-email)]]]} + :evt.user.form/grant-role + (fn [{:keys [db]} [_ role]] + (let [new-role-email (-> db :form.role/fields role :new-role/email (valid/validate valid/user-email-schema))] + (if (:errors new-role-email) + {:fx [[:dispatch [:evt.error/set-validation-errors (valid/error-msg new-role-email)]]]} {:http-xhrio {:method :post - :uri (base-uri "/users/new-role/admin") + :uri (base-uri (str "/users/new-role/" (name role))) :headers {:cookie (:user/cookie db)} :params {:users {:new-role - {(list :admin :with [new-admin-email]) + {(list role :with [new-role-email]) {:user/name '? :user/roles [{:role/name '? :role/date-granted '?}]}}}} :format (edn-request-format {:keywords? true}) :response-format (edn-response-format {:keywords? true}) - :on-success [:fx.http/grant-admin-success] + :on-success [:fx.http/grant-role-success role] :on-failure [:fx.http/failure]}})))) ;; ---------- Post ---------- @@ -136,7 +142,7 @@ (let [post (-> db :app/posts (get post-id))] (if (= :edit (:post/mode post)) {:db (assoc-in db [:app/posts post-id :post/mode] :read) - :fx [[:dispatch [:evt.post.form/clear-form]] + :fx [[:dispatch [:evt.form/clear :form/fields]] [:dispatch [:evt.error/clear-errors]]]} {:db (assoc-in db [:app/posts post-id :post/mode] :edit) :fx [[:dispatch [:evt.post.form/autofill post-id]]]})))) @@ -272,9 +278,9 @@ (assoc post id value))) (rf/reg-event-db - :evt.post.form/clear-form - (fn [db _] - (dissoc db :form/fields))) + :evt.form/clear + (fn [db [_ form]] + (dissoc db form))) ;; post deletion diff --git a/client/web/src/flybot/client/web/core/dom/page/admin.cljs b/client/web/src/flybot/client/web/core/dom/page/admin.cljs index 93e74cde..05c41913 100644 --- a/client/web/src/flybot/client/web/core/dom/page/admin.cljs +++ b/client/web/src/flybot/client/web/core/dom/page/admin.cljs @@ -3,45 +3,51 @@ [flybot.client.web.core.dom.common.svg :as svg] [re-frame.core :as rf])) -(defn admin? - [] - (some #{:admin} (->> @(rf/subscribe [:subs/pattern '{:app/user {:user/roles [{:role/name ?} ?x]}}]) - (map :role/name)))) +(defn has-role? + [role] + (some #{role} (->> @(rf/subscribe [:subs/pattern '{:app/user {:user/roles [{:role/name ?} ?x]}}]) + (map :role/name)))) ;;---------- Button ---------- -(defn submit-admin-button - [] +(defn submit-role-button + [role] [:button {:type "button" - :on-click #(rf/dispatch [:evt.user.form/grant-admin])} + :on-click #(rf/dispatch [:evt.user.form/grant-role role])} svg/done-icon]) ;;---------- From ---------- -(defn admin-form - [] - [:form - [:fieldset - [:label {:for "add-admin"} "Email of new admin:"] - [:br] - [:input - {:type "text" - :name "add-admin" - :placeholder "somebody@basecity.com" - :value @(rf/subscribe [:subs/pattern '{:form/fields {:new-admin/email ?x}}]) - :on-change #(rf/dispatch [:evt.post.form/set-field - :new-admin/email - (.. % -target -value)])}]]]) +(defn grant-role-from + [role] + (let [role-str (name role) + for-val (str "add-role" role-str)] + [:form + [:fieldset + [:label {:for for-val} (str "Email of new " role-str ":")] + [:br] + [:input + {:type "text" + :name for-val + :placeholder "somebody@basecity.com" + :value @(rf/subscribe [:subs/pattern {:form.role/fields {role '{:new-role/email ?x}}}]) + :on-change #(rf/dispatch [:evt.role.form/set-field + role + :new-role/email + (.. % -target -value)])}]]])) ;;---------- Admin div ---------- (defn admin-panel [] - (when (admin?) + (when (has-role? :owner) [:section.container.admin [:h1 "Admin"] [:<> [errors "admin-page" [:validation-errors :failure-http-result]] [:form - [submit-admin-button]] - [admin-form]]])) \ No newline at end of file + [submit-role-button :admin]] + [grant-role-from :admin] + [:form + [submit-role-button :owner]] + [grant-role-from :owner]]])) \ No newline at end of file From 46a2086d837ed59a18cb301458351bf35d62f6f5 Mon Sep 17 00:00:00 2001 From: skydread1 Date: Tue, 25 Jul 2023 15:53:17 +0800 Subject: [PATCH 3/9] Refactor form clear events --- client/mobile/src/flybot/client/mobile/core/db/event.cljs | 4 ++-- client/web/src/flybot/client/web/core/db/event.cljs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/mobile/src/flybot/client/mobile/core/db/event.cljs b/client/mobile/src/flybot/client/mobile/core/db/event.cljs index 8f89d3ee..ed3f870b 100644 --- a/client/mobile/src/flybot/client/mobile/core/db/event.cljs +++ b/client/mobile/src/flybot/client/mobile/core/db/event.cljs @@ -12,7 +12,7 @@ (fn [_ [_ {:keys [posts]}]] (let [{:post/keys [id] :as post} (:new-post posts)] {:fx [[:dispatch [:evt.post/add-post post]] - [:dispatch [:evt.post.form/clear-form]] + [:dispatch [:evt.form/clear :form/fields]] [:dispatch [:evt.error/clear-errors]] [:dispatch [:evt.post/set-modes :read]] [:fx.log/message ["Post " id " sent."]] @@ -102,7 +102,7 @@ :evt.post.edit/cancel (fn [_ [_ post-id]] (let [go-back-screen (if (temporary-id? (str post-id)) "posts-list" "post-read")] - {:fx [[:dispatch [:evt.post.form/clear-form]] + {:fx [[:dispatch [:evt.form/clear :form/fields]] [:dispatch [:evt.error/clear-errors]] [:dispatch [:evt.nav/navigate go-back-screen post-id]]]}))) diff --git a/client/web/src/flybot/client/web/core/db/event.cljs b/client/web/src/flybot/client/web/core/db/event.cljs index 52122bcc..60c87838 100644 --- a/client/web/src/flybot/client/web/core/db/event.cljs +++ b/client/web/src/flybot/client/web/core/db/event.cljs @@ -16,7 +16,7 @@ (fn [_ [_ {:keys [posts]}]] (let [{:post/keys [id] :as post} (:new-post posts)] {:fx [[:dispatch [:evt.post/add-post post]] - [:dispatch [:evt.post.form/clear-form]] + [:dispatch [:evt.form/clear :form/fields]] [:dispatch [:evt.error/clear-errors]] [:dispatch [:evt.post/set-modes :read]] [:fx.log/message ["Post " id " sent."]]]}))) From 896e6201351cf9acc76b212e176159796d42b9cd Mon Sep 17 00:00:00 2001 From: skydread1 Date: Tue, 25 Jul 2023 16:41:59 +0800 Subject: [PATCH 4/9] Add admin-test --- .../client/web/core/dom/page/admin_test.cljs | 59 +++++++++++++++++++ .../test/flybot/client/web/test_runner.cljs | 13 ++-- 2 files changed, 66 insertions(+), 6 deletions(-) create mode 100644 client/web/test/flybot/client/web/core/dom/page/admin_test.cljs diff --git a/client/web/test/flybot/client/web/core/dom/page/admin_test.cljs b/client/web/test/flybot/client/web/core/dom/page/admin_test.cljs new file mode 100644 index 00000000..c11d7f63 --- /dev/null +++ b/client/web/test/flybot/client/web/core/dom/page/admin_test.cljs @@ -0,0 +1,59 @@ +(ns flybot.client.web.core.dom.page.admin-test + (:require [cljs.test :refer-macros [deftest is testing use-fixtures]] + [day8.re-frame.test :as rf-test] + [flybot.client.web.core.db] + [flybot.client.web.core.router :as router] + [flybot.common.test-sample-data :as s] + [flybot.common.utils :as utils] + [re-frame.core :as rf])) + +;; ---------- Fixtures ---------- + +(use-fixtures :once + {:before (fn [] (router/init-routes!))}) + +(defn test-fixtures + "Set local storage values and initialize DB with sample data." + [] + ;; Mock local storage store + (rf/reg-cofx + :cofx.app/local-store-theme + (fn [coeffects _] + (assoc coeffects :local-store-theme :dark))) + ;; Mock success http request + (rf/reg-fx :http-xhrio + (fn [_] + (rf/dispatch [:fx.http/all-success s/init-data]))) + ;; Initialize db + (rf/dispatch [:evt.app/initialize])) + +(deftest add-roles + (with-redefs [utils/mk-date (constantly s/alice-date-granted)] + (rf-test/run-test-sync + (test-fixtures) + (let [{:user/keys [email] :as editor-alice} s/alice-user + admin-alice (update editor-alice :user/roles conj + [#:role{:name :admin :date-granted s/alice-date-granted}]) + role-form (rf/subscribe [:subs/pattern '{:form.role/fields ?x}]) + errors (rf/subscribe [:subs/pattern '{:app/errors ?x}])] + + ;;---------- GRANT ROLE ROLE ERROR + (rf/dispatch [:evt.role.form/set-field :admin :new-role/email "email@wrong.com"]) + ;; Send role but validation error + (rf/dispatch [:evt.user.form/grant-role :admin]) + (testing "Form not cleared and error added to db." + (is @role-form) + (is @errors)) + + ;;---------- GRANT ROLE ROLE SUCCESS + (rf/reg-fx :http-xhrio + (fn [_] (rf/dispatch + [:fx.http/grant-role-success + {:users {:new-role {:admin admin-alice}}}]))) + ;; fill the new role form + (rf/dispatch [:evt.role.form/set-field :admin :new-role/email email]) + ;; grant new role to user in the server + (rf/dispatch [:evt.user.form/grant-role :admin]) + (testing "Form and error cleared." + (is (not @role-form)) + (is (not @errors))))))) \ No newline at end of file diff --git a/client/web/test/flybot/client/web/test_runner.cljs b/client/web/test/flybot/client/web/test_runner.cljs index 9c448b82..baa8eb2f 100644 --- a/client/web/test/flybot/client/web/test_runner.cljs +++ b/client/web/test/flybot/client/web/test_runner.cljs @@ -1,12 +1,13 @@ (ns flybot.client.web.test-runner (:require - [figwheel.main.testing :refer-macros [run-tests-async]] + [figwheel.main.testing :refer-macros [run-tests-async]] ;; require all the namespaces that have tests in them - [flybot.client.web.core.db-test] - [flybot.client.web.core.dom.common.link-test] - [flybot.client.web.core.dom.page.post-test] - [flybot.client.web.core.router-test] - [flybot.common.validation.markdown-test])) + [flybot.client.web.core.db-test] + [flybot.client.web.core.dom.common.link-test] + [flybot.client.web.core.dom.page.admin-test] + [flybot.client.web.core.dom.page.post-test] + [flybot.client.web.core.router-test] + [flybot.common.validation.markdown-test])) (defn -main [& args] ;; this needs to be the last statement in the main function so that it can From c658c057568504a609f78868f392d8b736ef2884 Mon Sep 17 00:00:00 2001 From: skydread1 Date: Tue, 25 Jul 2023 16:49:39 +0800 Subject: [PATCH 5/9] Improve admin css --- .../src/flybot/client/web/core/dom/page/admin.cljs | 14 ++++++++------ resources/public/css/style.css | 4 ++-- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/client/web/src/flybot/client/web/core/dom/page/admin.cljs b/client/web/src/flybot/client/web/core/dom/page/admin.cljs index 05c41913..99da8887 100644 --- a/client/web/src/flybot/client/web/core/dom/page/admin.cljs +++ b/client/web/src/flybot/client/web/core/dom/page/admin.cljs @@ -45,9 +45,11 @@ [:h1 "Admin"] [:<> [errors "admin-page" [:validation-errors :failure-http-result]] - [:form - [submit-role-button :admin]] - [grant-role-from :admin] - [:form - [submit-role-button :owner]] - [grant-role-from :owner]]])) \ No newline at end of file + [:div + [:form + [submit-role-button :admin]] + [grant-role-from :admin]] + [:div + [:form + [submit-role-button :owner]] + [grant-role-from :owner]]]])) \ No newline at end of file diff --git a/resources/public/css/style.css b/resources/public/css/style.css index 61f91135..62117044 100644 --- a/resources/public/css/style.css +++ b/resources/public/css/style.css @@ -586,8 +586,8 @@ section .team img { border-bottom-color: var(--border-primary-color); } -section.admin h1 { - text-align: center; +.admin div { + margin: 1rem; } /* Blog Page */ From 6f297bc679d1a3bc3886f5805a31d946abddbe43 Mon Sep 17 00:00:00 2001 From: skydread1 Date: Tue, 25 Jul 2023 16:58:40 +0800 Subject: [PATCH 6/9] rename :new-role/email to :user/email --- client/common/src/flybot/client/common/db/event.cljs | 2 +- client/web/src/flybot/client/web/core/dom/page/admin.cljs | 4 ++-- .../web/test/flybot/client/web/core/dom/page/admin_test.cljs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/client/common/src/flybot/client/common/db/event.cljs b/client/common/src/flybot/client/common/db/event.cljs index 80b83d70..630e3d6e 100644 --- a/client/common/src/flybot/client/common/db/event.cljs +++ b/client/common/src/flybot/client/common/db/event.cljs @@ -101,7 +101,7 @@ (rf/reg-event-fx :evt.user.form/grant-role (fn [{:keys [db]} [_ role]] - (let [new-role-email (-> db :form.role/fields role :new-role/email (valid/validate valid/user-email-schema))] + (let [new-role-email (-> db :form.role/fields role :user/email (valid/validate valid/user-email-schema))] (if (:errors new-role-email) {:fx [[:dispatch [:evt.error/set-validation-errors (valid/error-msg new-role-email)]]]} {:http-xhrio {:method :post diff --git a/client/web/src/flybot/client/web/core/dom/page/admin.cljs b/client/web/src/flybot/client/web/core/dom/page/admin.cljs index 99da8887..eddb67e9 100644 --- a/client/web/src/flybot/client/web/core/dom/page/admin.cljs +++ b/client/web/src/flybot/client/web/core/dom/page/admin.cljs @@ -30,10 +30,10 @@ {:type "text" :name for-val :placeholder "somebody@basecity.com" - :value @(rf/subscribe [:subs/pattern {:form.role/fields {role '{:new-role/email ?x}}}]) + :value @(rf/subscribe [:subs/pattern {:form.role/fields {role '{:user/email ?x}}}]) :on-change #(rf/dispatch [:evt.role.form/set-field role - :new-role/email + :user/email (.. % -target -value)])}]]])) ;;---------- Admin div ---------- diff --git a/client/web/test/flybot/client/web/core/dom/page/admin_test.cljs b/client/web/test/flybot/client/web/core/dom/page/admin_test.cljs index c11d7f63..a9da16ae 100644 --- a/client/web/test/flybot/client/web/core/dom/page/admin_test.cljs +++ b/client/web/test/flybot/client/web/core/dom/page/admin_test.cljs @@ -38,7 +38,7 @@ errors (rf/subscribe [:subs/pattern '{:app/errors ?x}])] ;;---------- GRANT ROLE ROLE ERROR - (rf/dispatch [:evt.role.form/set-field :admin :new-role/email "email@wrong.com"]) + (rf/dispatch [:evt.role.form/set-field :admin :user/email "email@wrong.com"]) ;; Send role but validation error (rf/dispatch [:evt.user.form/grant-role :admin]) (testing "Form not cleared and error added to db." @@ -51,7 +51,7 @@ [:fx.http/grant-role-success {:users {:new-role {:admin admin-alice}}}]))) ;; fill the new role form - (rf/dispatch [:evt.role.form/set-field :admin :new-role/email email]) + (rf/dispatch [:evt.role.form/set-field :admin :user/email email]) ;; grant new role to user in the server (rf/dispatch [:evt.user.form/grant-role :admin]) (testing "Form and error cleared." From a13735ad4d732d5bb5f6462f350b27477fd6292f Mon Sep 17 00:00:00 2001 From: skydread1 Date: Tue, 25 Jul 2023 17:16:43 +0800 Subject: [PATCH 7/9] Add message for non-admin users --- .../src/flybot/client/web/core/dom/page/admin.cljs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/client/web/src/flybot/client/web/core/dom/page/admin.cljs b/client/web/src/flybot/client/web/core/dom/page/admin.cljs index eddb67e9..9a02894b 100644 --- a/client/web/src/flybot/client/web/core/dom/page/admin.cljs +++ b/client/web/src/flybot/client/web/core/dom/page/admin.cljs @@ -40,9 +40,9 @@ (defn admin-panel [] - (when (has-role? :owner) - [:section.container.admin - [:h1 "Admin"] + [:section.container.admin + [:h1 "Admin"] + (if (has-role? :owner) [:<> [errors "admin-page" [:validation-errors :failure-http-result]] [:div @@ -52,4 +52,7 @@ [:div [:form [submit-role-button :owner]] - [grant-role-from :owner]]]])) \ No newline at end of file + [grant-role-from :owner]]] + [:div + [:h2 "You do not have the required permissions."] + [:p "This section is dedicated to the owners of the website."]])]) \ No newline at end of file From 11b55b8f1f79b0b823e1100490dbf0c6e2828ce9 Mon Sep 17 00:00:00 2001 From: skydread1 Date: Tue, 25 Jul 2023 17:19:41 +0800 Subject: [PATCH 8/9] Fix typos --- client/web/src/flybot/client/web/core/dom/page/admin.cljs | 6 +++--- .../test/flybot/client/web/core/dom/page/admin_test.cljs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/client/web/src/flybot/client/web/core/dom/page/admin.cljs b/client/web/src/flybot/client/web/core/dom/page/admin.cljs index 9a02894b..8f0849e5 100644 --- a/client/web/src/flybot/client/web/core/dom/page/admin.cljs +++ b/client/web/src/flybot/client/web/core/dom/page/admin.cljs @@ -18,7 +18,7 @@ ;;---------- From ---------- -(defn grant-role-from +(defn grant-role-form [role] (let [role-str (name role) for-val (str "add-role" role-str)] @@ -48,11 +48,11 @@ [:div [:form [submit-role-button :admin]] - [grant-role-from :admin]] + [grant-role-form :admin]] [:div [:form [submit-role-button :owner]] - [grant-role-from :owner]]] + [grant-role-form :owner]]] [:div [:h2 "You do not have the required permissions."] [:p "This section is dedicated to the owners of the website."]])]) \ No newline at end of file diff --git a/client/web/test/flybot/client/web/core/dom/page/admin_test.cljs b/client/web/test/flybot/client/web/core/dom/page/admin_test.cljs index a9da16ae..cdc702f3 100644 --- a/client/web/test/flybot/client/web/core/dom/page/admin_test.cljs +++ b/client/web/test/flybot/client/web/core/dom/page/admin_test.cljs @@ -37,7 +37,7 @@ role-form (rf/subscribe [:subs/pattern '{:form.role/fields ?x}]) errors (rf/subscribe [:subs/pattern '{:app/errors ?x}])] - ;;---------- GRANT ROLE ROLE ERROR + ;;---------- GRANT ROLE ERROR (rf/dispatch [:evt.role.form/set-field :admin :user/email "email@wrong.com"]) ;; Send role but validation error (rf/dispatch [:evt.user.form/grant-role :admin]) @@ -45,7 +45,7 @@ (is @role-form) (is @errors)) - ;;---------- GRANT ROLE ROLE SUCCESS + ;;---------- GRANT ROLE SUCCESS (rf/reg-fx :http-xhrio (fn [_] (rf/dispatch [:fx.http/grant-role-success From a7b7b1cfab5d4534c594cf32571b6413c24a60a0 Mon Sep 17 00:00:00 2001 From: skydread1 Date: Wed, 26 Jul 2023 09:13:21 +0800 Subject: [PATCH 9/9] Rename for-val in grant-role-form --- client/web/src/flybot/client/web/core/dom/page/admin.cljs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/web/src/flybot/client/web/core/dom/page/admin.cljs b/client/web/src/flybot/client/web/core/dom/page/admin.cljs index 8f0849e5..83e6dc57 100644 --- a/client/web/src/flybot/client/web/core/dom/page/admin.cljs +++ b/client/web/src/flybot/client/web/core/dom/page/admin.cljs @@ -21,7 +21,7 @@ (defn grant-role-form [role] (let [role-str (name role) - for-val (str "add-role" role-str)] + for-val (str "email-input-name-" role-str)] [:form [:fieldset [:label {:for for-val} (str "Email of new " role-str ":")]