diff --git a/src/braid/core/api.cljc b/src/braid/core/api.cljc index 04b215964..3ec970d3a 100644 --- a/src/braid/core/api.cljc +++ b/src/braid/core/api.cljc @@ -63,6 +63,15 @@ {:pre [(util/valid? braid.core.client.ui.views.user-header/UserHeaderItem config)]} (swap! braid.core.client.ui.views.user-header/user-header-menu-items conj config)) + (defn register-user-profile-item! + "Add a new section on the user profile page + Expects a map with the following keys + :view reagent component + :priority number, for ordering" + [config] + {:pre [(util/valid? braid.core.client.ui.views.pages.me/profile-item-dataspec config)]} + (swap! braid.core.client.ui.views.pages.me/user-profile-items conj config)) + (defn register-thread-header-item! "Adds a new view to a thread's header. Expects a map with the following keys: diff --git a/src/braid/core/client/ui/views/pages/me.cljs b/src/braid/core/client/ui/views/pages/me.cljs index 16a53536f..db073238a 100644 --- a/src/braid/core/client/ui/views/pages/me.cljs +++ b/src/braid/core/client/ui/views/pages/me.cljs @@ -3,10 +3,18 @@ [braid.core.client.routes :as routes] [braid.core.client.ui.views.upload :refer [avatar-upload-view]] [braid.core.common.util :refer [valid-nickname?]] + [braid.core.hooks :as hooks] [clojure.string :as string] [re-frame.core :refer [dispatch subscribe]] [reagent.core :as r])) +(def profile-item-dataspec + {:priority number? + :view fn?}) + +(defonce user-profile-items + (hooks/register! (atom []) [profile-item-dataspec])) + (defn nickname-view [] (let [format-error (r/atom false) @@ -102,10 +110,17 @@ [] [:div.page.me [:div.title "Me!"] - [:div.content - [nickname-view] - [avatar-view] - [password-view] - [:p - [:a {:href (routes/system-page-path {:page-id "global-settings"})} - "Go to Global Settings"]]]]) + (into + [:div.content] + (conj + (->> @user-profile-items + (sort-by :priority) + reverse + (mapv (fn [el] + [(:view el)]))) + [nickname-view] + [avatar-view] + [password-view] + [:p + [:a {:href (routes/system-page-path {:page-id "global-settings"})} + "Go to Global Settings"]]))]) diff --git a/src/braid/core/modules.cljc b/src/braid/core/modules.cljc index 19744e5d5..1f2852567 100644 --- a/src/braid/core/modules.cljc +++ b/src/braid/core/modules.cljc @@ -15,6 +15,7 @@ [braid.notices.core] [braid.permalinks.core] [braid.popovers.core] + [braid.profile.core] [braid.quests.core] [braid.rss.core] [braid.search.core] @@ -42,6 +43,7 @@ (braid.notices.core/init!) (braid.permalinks.core/init!) (braid.popovers.core/init!) + (braid.profile.core/init!) (braid.quests.core/init!) (braid.rss.core/init!) (braid.stars.core/init!) diff --git a/src/braid/profile/core.cljc b/src/braid/profile/core.cljc new file mode 100644 index 000000000..660cdffe3 --- /dev/null +++ b/src/braid/profile/core.cljc @@ -0,0 +1,103 @@ +(ns braid.profile.core + "Allows users to set and update their profile." + (:require + [braid.core.api :as core] + #?@(:cljs + [[reagent.core :as r] + [re-frame.core :refer [subscribe dispatch]]] + + :clj + [[datomic.api :as d] + [braid.core.server.db :as db]]))) + +(defn profile-view + [] + #?(:cljs + (let [format-error (r/atom false) + error (r/atom nil) + set-format-error! (fn [error?] (reset! format-error error?)) + set-error! (fn [err] (reset! error err)) + profile (subscribe [:braid.profile/user-profile]) + new-profile (r/atom "")] + (fn [] + [:div.setting + [:h2 "Update profile"] + [:div.profile + (when @profile + [:div.current-profile + {:style {:white-space "pre-wrap"}} + @profile]) + (when @error + [:span.error @error]) + [:form {:on-submit (fn [e] + (.preventDefault e) + (set-error! nil) + (dispatch [:braid.profile/set-user-profile! @new-profile]))} + [:textarea.new-profile + {:class (when @format-error "error") + :style {:width "50%"} + :rows 8 + :on-change (fn [e] + (->> (.. e -target -value) + (reset! new-profile)))} + @profile] + [:div + [:input {:type "submit" :value "Update"}]]]]])))) + +(defn init! [] + #?(:cljs + (do + (core/register-state! + {:braid.profile/user-profile ""} + {:braid.profile/user-profile string?}) + + (core/register-events! + {:braid.profile/set-user-profile! + (fn [{db :db} [_ new-profile]] + (when-not (= (db :braid.profile/user-profile) new-profile) + {:db (assoc db :braid.profile/user-profile new-profile) + :websocket-send [[:braid.profile.ws/set-user-profile! new-profile]]}))}) + + (core/register-subs! + {:braid.profile/user-profile + (fn [db _] + (:braid.profile/user-profile db))}) + + (core/register-incoming-socket-message-handlers! + {:braid.profile.ws/set-user-profile! + (fn [_ new-profile] + (dispatch [:braid.profile/set-user-profile! new-profile]))}) + + (core/register-initial-user-data-handler! + (fn + [db data] + (assoc db :braid.profile/user-profile + (data :braid.profile/user-profile)))) + + (core/register-user-profile-item! + {:priority 10 + :view profile-view})) + + :clj + (do + (core/register-db-schema! + [{:db/ident :user/profile + :db/valueType :db.type/string + :db/cardinality :db.cardinality/one}]) + + (core/register-initial-user-data! + (fn [user-id] + {:braid.profile/user-profile + (d/q '[:find ?user-profile . + :in $ ?user-id + :where + [?u :user/id ?user-id] + [?u :user/profile ?user-profile]] + (db/db) + user-id)})) + + (core/register-server-message-handlers! + {:braid.profile.ws/set-user-profile! + (fn [{user-id :user-id new-profile :?data}] + {:db-run-txns! [[:db/add [:user/id user-id] :user/profile new-profile]] + :chsk-send! [user-id [:braid.profile.ws/set-user-profile! new-profile]]})}))))