UX improvements + address validation
* error/success flash notification support * disable spellcheck for address editbox * validate user's Ethereum address when saving * update button state styles
This commit is contained in:
parent
1663669163
commit
4e627be1e0
|
@ -1,4 +1,4 @@
|
|||
{
|
||||
:test true
|
||||
:jdbc-database-url "jdbc:postgresql://localhost/commiteth_test?user=commiteth&password=commiteth"
|
||||
:jdbc-database-url "jdbc:postgresql://localhost/commiteth?user=commiteth&password=commiteth"
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
[commiteth.db.repositories :as repositories]
|
||||
[commiteth.db.bounties :as bounties-db]
|
||||
[commiteth.bounties :as bounties]
|
||||
[commiteth.eth.core :as eth]
|
||||
[commiteth.github.core :as github]
|
||||
[clojure.tools.logging :as log]
|
||||
[commiteth.eth.core :as eth]
|
||||
|
@ -97,11 +98,9 @@
|
|||
:description "commitETH API"}}}}
|
||||
|
||||
(context "/api" []
|
||||
|
||||
(context "/bounties" []
|
||||
(GET "/all" []
|
||||
(ok (bounties-db/list-all-bounties))))
|
||||
|
||||
(context "/user" []
|
||||
(GET "/" []
|
||||
:auth-rules authenticated?
|
||||
|
@ -111,10 +110,15 @@
|
|||
:auth-rules authenticated?
|
||||
:body-params [user-id :- Long, address :- String]
|
||||
:summary "Update user address"
|
||||
(let [result (users/update-user-address user-id address)]
|
||||
(if-not (eth/valid-address? address)
|
||||
{:status 400
|
||||
:body "Invalid Ethereum address"}
|
||||
(let [result (users/update-user-address
|
||||
user-id
|
||||
address)]
|
||||
(if (= 1 result)
|
||||
(ok)
|
||||
(internal-server-error))))
|
||||
(internal-server-error)))))
|
||||
(GET "/repositories" []
|
||||
:auth-rules authenticated?
|
||||
:current-user user
|
||||
|
@ -138,8 +142,7 @@
|
|||
:auth-rules authenticated?
|
||||
:current-user user
|
||||
(ok (map #(conj % (let [balance (:balance %)]
|
||||
{:balance-eth (eth/hex->eth balance 6)
|
||||
:balance-wei (eth/hex->big-integer balance)}))
|
||||
{:balance-eth (eth/hex->eth balance 6)}))
|
||||
(bounties-db/list-owner-bounties (:id user)))))
|
||||
(POST "/repository/toggle" {:keys [params]}
|
||||
:auth-rules authenticated?
|
||||
|
|
|
@ -9,12 +9,13 @@
|
|||
:value @val-ratom
|
||||
:on-change #(reset! val-ratom (-> % .-target .-value))})]))
|
||||
|
||||
(defn dropdown [title val-ratom items]
|
||||
(defn dropdown [props title val-ratom items]
|
||||
(fn []
|
||||
(if (= 1 (count items))
|
||||
(reset! val-ratom (first items)))
|
||||
[:select.ui.basic.selection.dropdown
|
||||
{:on-change #(reset! val-ratom (-> % .-target .-value))}
|
||||
(merge props {:on-change
|
||||
#(reset! val-ratom (-> % .-target .-value))})
|
||||
(doall (for [item items]
|
||||
^{:key item} [:option
|
||||
{:value item}
|
||||
|
|
|
@ -22,6 +22,16 @@
|
|||
[re-frisk.core :refer [enable-re-frisk!]])
|
||||
(:import goog.History))
|
||||
|
||||
|
||||
(defn flash-message-pane []
|
||||
(let [flash-message (rf/subscribe [:flash-message])]
|
||||
(fn []
|
||||
(when-let [[type message] @flash-message]
|
||||
[:div.flash-message {:class (name type)}
|
||||
[:i.close.icon {:on-click #(rf/dispatch [:clear-flash-message])}]
|
||||
[:p message]]))
|
||||
))
|
||||
|
||||
(def user-dropdown-open? (r/atom false))
|
||||
|
||||
(defn user-dropdown [user items]
|
||||
|
@ -38,12 +48,13 @@
|
|||
[:div.item
|
||||
[:span.dropdown.icon]]
|
||||
(into menu
|
||||
(for [[target caption] items]
|
||||
(for [[target caption props] items]
|
||||
^{:key target} [:div.item
|
||||
[:a
|
||||
(merge props
|
||||
(if (keyword? target)
|
||||
{:on-click #(rf/dispatch [target])}
|
||||
{:href target})
|
||||
{:href target}))
|
||||
caption]]))]))
|
||||
|
||||
|
||||
|
@ -53,8 +64,8 @@
|
|||
(if @user
|
||||
[:div.ui.text.menu.user-component
|
||||
[:div.item
|
||||
[user-dropdown @user [[:update-address "Update address"]
|
||||
["/logout" "Sign out"]]]]]
|
||||
[user-dropdown @user [[:update-address "Update address" {}]
|
||||
["/logout" "Sign out" {:class "logout-link"}]]]]]
|
||||
[:a.ui.button.small {:href js/authorizeUrl} "Sign in"]))))
|
||||
|
||||
(defn tabs []
|
||||
|
@ -74,7 +85,8 @@
|
|||
|
||||
|
||||
(defn page-header []
|
||||
(let [user (rf/subscribe [:user])]
|
||||
(let [user (rf/subscribe [:user])
|
||||
flash-message (rf/subscribe [:flash-message])]
|
||||
(fn []
|
||||
[:div.vertical.segment.commiteth-header
|
||||
[:div.ui.grid.container
|
||||
|
@ -82,7 +94,9 @@
|
|||
[:div.ui.image
|
||||
[:img.left.aligned {:src "/img/logo.svg"}]]]
|
||||
[:div.four.wide.column
|
||||
[user-component @user]]
|
||||
(if @flash-message
|
||||
[flash-message-pane]
|
||||
[user-component @user])]
|
||||
(when-not @user
|
||||
[:div.ui.text.content
|
||||
[:div.ui.divider.hidden]
|
||||
|
@ -117,7 +131,6 @@
|
|||
(fn []
|
||||
[:div.ui.pusher
|
||||
[page-header]
|
||||
;; [error-pane]
|
||||
[:div.ui.vertical.segment
|
||||
[:div.ui.container
|
||||
[:div.ui.grid.stackable
|
||||
|
|
|
@ -25,14 +25,14 @@
|
|||
(assoc-in db path value)))
|
||||
|
||||
(reg-event-db
|
||||
:set-error
|
||||
(fn [db [_ text]]
|
||||
(assoc db :error text)))
|
||||
:set-flash-message
|
||||
(fn [db [_ type text]]
|
||||
(assoc db :flash-message [type text])))
|
||||
|
||||
(reg-event-db
|
||||
:clear-error
|
||||
:clear-flash-message
|
||||
(fn [db _]
|
||||
(dissoc db :error)))
|
||||
(dissoc db :flash-message)))
|
||||
|
||||
(reg-event-db
|
||||
:set-active-page
|
||||
|
@ -181,9 +181,21 @@
|
|||
:save-user-address
|
||||
(fn [{:keys [db]} [_ user-id address]]
|
||||
(prn "save-user-address" user-id address)
|
||||
{:db db
|
||||
{:db (assoc db :updating-address true)
|
||||
:http {:method POST
|
||||
:url "/api/user/address"
|
||||
:on-success #(println "on-success" %)
|
||||
:on-error #(println "on-error" %)
|
||||
:on-success #(do
|
||||
(dispatch [:assoc-in [:user [:address] address]])
|
||||
(dispatch [:set-flash-message
|
||||
:success
|
||||
"Address saved"]))
|
||||
:on-error #(dispatch [:set-flash-message
|
||||
:error
|
||||
(:response %)])
|
||||
:finally #(dispatch [:clear-updating-address])
|
||||
:params {:user-id user-id :address address}}}))
|
||||
|
||||
(reg-event-db
|
||||
:clear-updating-address
|
||||
(fn [db _]
|
||||
(dissoc db :updating-address)))
|
||||
|
|
|
@ -22,9 +22,9 @@
|
|||
(:repos db)))
|
||||
|
||||
(reg-sub
|
||||
:error
|
||||
:flash-message
|
||||
(fn [db _]
|
||||
(:error db)))
|
||||
(:flash-message db)))
|
||||
|
||||
(reg-sub
|
||||
:all-bounties
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
(defn update-address-page []
|
||||
(let [user (rf/subscribe [:user])
|
||||
updating-address (rf/subscribe [:get-in [:updating-address]])
|
||||
web3 (.-web3 js/window)
|
||||
web3-accounts (into [] (when-not (nil? web3) (-> web3
|
||||
.-eth
|
||||
|
@ -21,7 +22,7 @@
|
|||
[:p "Placeholder text for explaining what an Ethereum address is."]
|
||||
[:div.field
|
||||
(if-not (empty? web3-accounts)
|
||||
[dropdown "Select address"
|
||||
[dropdown {:class "address-input"} "Select address"
|
||||
address
|
||||
(into []
|
||||
(for [acc web3-accounts]
|
||||
|
@ -29,9 +30,13 @@
|
|||
[:div.ui.input.address-input
|
||||
[input address {:placeholder "0x0000000000000000000000000000000000000000"
|
||||
:auto-complete "off"
|
||||
:auto-correct "off"
|
||||
:spell-check "false"
|
||||
:max-length 42}]])]
|
||||
[:button.ui.button {:on-click
|
||||
[:button.ui.button (merge {:on-click
|
||||
#(rf/dispatch [:save-user-address
|
||||
(:id @user)
|
||||
@address])}
|
||||
(when @updating-address
|
||||
{:class "busy loading"}))
|
||||
"Update"]]])))
|
||||
|
|
|
@ -13,9 +13,16 @@
|
|||
|
||||
|
||||
.ui.button {
|
||||
color: #2f3f44;
|
||||
background-color: #b0f1ee;
|
||||
white-space: nowrap;
|
||||
padding: 0.9em 1.2em 0.9em;
|
||||
&:hover {
|
||||
background-color: #bdf8f5;
|
||||
}
|
||||
&:active,&:focus {
|
||||
background-color: #97ebe7;
|
||||
}
|
||||
}
|
||||
.ui.small.button {
|
||||
font-size: 15px!important;
|
||||
|
@ -87,7 +94,6 @@ span.dropdown.icon {
|
|||
.ui.dropdown .item {
|
||||
color: #fff!important;
|
||||
opacity: 0.98;
|
||||
|
||||
}
|
||||
|
||||
.ui.menu .ui.dropdown .menu>.item:hover, .ui.menu .ui.dropdown .menu>.selected.item {
|
||||
|
@ -95,28 +101,31 @@ span.dropdown.icon {
|
|||
}
|
||||
|
||||
.ui.input.address-input {
|
||||
color: #e7e7e7!important;
|
||||
font-size: .8em;
|
||||
width: 360px!important;
|
||||
}
|
||||
|
||||
|
||||
.ui.selection.dropdown.address-input option {
|
||||
color: #e7e7e7;
|
||||
background-color: yellow!important;
|
||||
}
|
||||
.ui.menu.transition.visible {
|
||||
font-family: 'postgrotesk';
|
||||
font-size: 1em;
|
||||
border-radius: 8px;
|
||||
border: none;
|
||||
padding: .5em;
|
||||
.item {
|
||||
:hover {
|
||||
color: #e96868;
|
||||
}
|
||||
a {
|
||||
.item>a {
|
||||
&:hover {
|
||||
color: #e96868;
|
||||
color:#1bb5c1;
|
||||
}
|
||||
color: #474951;
|
||||
}
|
||||
}
|
||||
|
||||
.logout-link {
|
||||
color: #e96868!important;
|
||||
}
|
||||
|
||||
.ui.attached.tabular {
|
||||
|
@ -189,9 +198,6 @@ span.dropdown.icon {
|
|||
padding: 0.8em 1em 1.1em!important;
|
||||
|
||||
flex-direction: row!important;
|
||||
// .ui.mini.circular.image {
|
||||
// margin: 0;
|
||||
// }
|
||||
}
|
||||
|
||||
.ui.cards>.card {
|
||||
|
@ -229,6 +235,28 @@ span.dropdown.icon {
|
|||
color: #a8aab1;
|
||||
}
|
||||
|
||||
.flash-message {
|
||||
&.success {
|
||||
background-color: #61deb0;
|
||||
}
|
||||
&.error {
|
||||
background-color: #e96868;
|
||||
}
|
||||
|
||||
color: #fff;
|
||||
border-radius: 8px;
|
||||
z-index: 100;
|
||||
padding: 1.4em 1em 1.4em;
|
||||
i {
|
||||
position: absolute;
|
||||
margin: 0;
|
||||
right: 1.5em;
|
||||
top: 1.5em;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.ui {
|
||||
font-family: 'postgrotesk'!important;
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
{:id 1
|
||||
:login "torvalds"
|
||||
:name "Linus Torvalds"
|
||||
:avatar_url ""
|
||||
:email nil
|
||||
:token "not null"
|
||||
:address "address"
|
||||
|
@ -32,6 +33,7 @@
|
|||
:login "torvalds"
|
||||
:name "Linus Torvalds"
|
||||
:email nil
|
||||
:avatar_url ""
|
||||
:token "not null"
|
||||
:address "address"
|
||||
:created nil}
|
||||
|
|
Loading…
Reference in New Issue