diff --git a/client/common/src/flybot/client/common/db/event.cljs b/client/common/src/flybot/client/common/db/event.cljs index 33d56d15..630e3d6e 100644 --- a/client/common/src/flybot/client/common/db/event.cljs +++ b/client/common/src/flybot/client/common/db/event.cljs @@ -57,7 +57,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 "."]]]}))) @@ -68,15 +68,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)] @@ -93,23 +99,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 :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 - :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 ---------- @@ -137,7 +143,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]]]})))) @@ -273,9 +279,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/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."]]]}))) 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 [] 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..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 @@ -3,45 +3,56 @@ [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-form + [role] + (let [role-str (name role) + for-val (str "email-input-name-" 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 '{:user/email ?x}}}]) + :on-change #(rf/dispatch [:evt.role.form/set-field + role + :user/email + (.. % -target -value)])}]]])) ;;---------- Admin div ---------- (defn admin-panel [] - (when (admin?) - [:section.container.admin - [:h1 "Admin"] + [:section.container.admin + [:h1 "Admin"] + (if (has-role? :owner) [:<> [errors "admin-page" [:validation-errors :failure-http-result]] - [:form - [submit-admin-button]] - [admin-form]]])) \ No newline at end of file + [:div + [:form + [submit-role-button :admin]] + [grant-role-form :admin]] + [:div + [:form + [submit-role-button :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 new file mode 100644 index 00000000..cdc702f3 --- /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 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]) + (testing "Form not cleared and error added to db." + (is @role-form) + (is @errors)) + + ;;---------- GRANT 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 :user/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 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 */