diff --git a/src/cljs/commiteth/manage_payouts.cljs b/src/cljs/commiteth/manage_payouts.cljs index 14df9a4..04cf22d 100644 --- a/src/cljs/commiteth/manage_payouts.cljs +++ b/src/cljs/commiteth/manage_payouts.cljs @@ -1,10 +1,10 @@ (ns commiteth.manage-payouts - (:require [re-frame.core :as rf] + (:require [reagent.core :as r] + [re-frame.core :as rf] [commiteth.routes :as routes] + [commiteth.model.bounty :as bnt] [commiteth.common :as common :refer [human-time]])) - - (defn pr-url [{owner :repo_owner pr-number :pr_number repo :repo_name}] @@ -12,17 +12,34 @@ (defn balance-badge [tla balance] + {:pre [(keyword? tla)]} (let [color (fn balance-badge-color [tla] (get {"ETH" "#57a7ed"} tla "#4360df")) - tla (if (keyword? tla) - (subs (str tla) 1) - tla)] - [:div.ph2.pv1.relative + tla (name tla)] + [:div.dib.ph2.pv1.relative {:style {:color (color tla)}} [:div.absolute.top-0.left-0.right-0.bottom-0.o-10.br2 {:style {:background-color (color tla)}}] [:span.pg-med (str tla " " balance)]])) +(defn usd-value-label [value-usd] + [:span + [:span.usd-value-label "Value "] + [:span.usd-balance-label (str "$" value-usd)]]) + +(defn token-balances [crypto-balances] + [:span ; TODO consider non DOM el react wrapping + (for [[tla balance] crypto-balances] + ^{:key tla} + [:div.dib.mr2 + [balance-badge tla balance]])]) + +(defn bounty-balance [{:keys [value-usd] :as bounty}] + [:div + [token-balances (bnt/crypto-balances bounty)] + [:div.dib.mr2.pv1 + [usd-value-label value-usd]]]) + (defn bounty-card [{owner :repo-owner repo-name :repo-name issue-title :issue-title @@ -31,78 +48,76 @@ tokens :tokens balance-eth :balance-eth value-usd :value-usd - :as bounty}] - + :as bounty} + {:keys [style] :as opts}] [:div [:a {:href (common/issue-url owner repo-name issue-number)} - [:span.db.f4.muted-blue.hover-black issue-title] - [:div.mt2 - [:span.f5.gray.pg-book (str owner "/" repo-name " #" issue-number) " — " (common/human-time updated)]] - [:div.cf.mt2 - [:div.fl.mr2 - [balance-badge "ETH" balance-eth]] - (for [[tla balance] tokens] - ^{:key tla} - [:div.fl.mr2 - [balance-badge tla balance]]) - [:div.fl.mr2.pv1 - [:span.usd-value-label "Value "] - [:span.usd-balance-label (str "$" value-usd)]]] + [:div.cf + [:div.fl.w-80 + [:span.db.f4.muted-blue.hover-black issue-title] + [:div.mt2 + [:span.f5.gray.pg-book (str owner "/" repo-name " #" issue-number)]]] + [:div.fl.w-20.tr + [:span.f5.gray.pg-book + {:on-click #(do (.preventDefault %) (prn (dissoc bounty :claims)))} + (common/human-time updated)]]]]]) - #_[:code (pr-str bounty)] +(defn confirm-button [bounty claim] + (let [paid? (bnt/paid? claim) + merged? (bnt/merged? claim)] + (when (and merged? (not paid?)) + [:button.f5.outline-0.bg-sob-blue.white.pv2.ph3.pg-med.br2.bn.pointer + (merge (if (and merged? (not paid?)) + {} + {:disabled true}) + {:on-click #(rf/dispatch [:confirm-payout claim])} + (when (and (or (bnt/confirming? bounty) + (bnt/bot-confirm-unmined? bounty)) + merged?) + {:class "busy loading" :disabled true})) + (if paid? + "Signed off" + "Confirm")]))) +(defn confirm-row [bounty claim] + [:div.cf + [:div.dt.fr + [:div.dtc.v-mid.pr3 + [bounty-balance bounty]] + [:div.dtc.v-mid + [confirm-button bounty claim]]]]) - #_(when (> claim-count 0) - [:span.open-claims-label (str claim-count " open claim" - (when (> claim-count 1) "s"))])]]) - -(defn claim-card [bounty claim] - #_(prn claim) - (let [{pr-state :pr_state - user-name :user_name +(defn claim-card [bounty claim {:keys [render-view-claim-button?] :as opts}] + (let [{user-name :user_name user-login :user_login - avatar-url :user_avatar_url - issue-id :issue_id - issue-title :issue_title} claim - merged? (= 1 (:pr_state claim)) - paid? (not-empty (:payout_hash claim)) - winner-login (:winner_login bounty) - bot-confirm-unmined? (empty? (:confirm_hash bounty)) - confirming? (:confirming? bounty) - updated (:updated bounty)] + avatar-url :user_avatar_url} claim + winner-login (:winner_login bounty)] [:div.pa2 - [:div.dt - {:class (when (and paid? (not (= user-login winner-login))) + [:div.flex.items-center + {:class (when (and (bnt/paid? claim) (not (= user-login winner-login))) "o-50")} - [:div.dtc.v-top + [:div [:img.br-100.w3 {:src avatar-url}]] - [:div.dtc.v-top.pl3 + [:div.pl3.flex-auto [:div - [:span.f4.muted-blue (or user-name user-login) " · " - (if paid? + [:span.f4.muted-blue + (or user-name user-login) " " + [:span.f5.o-60 (when user-name (str "@" user-login "") )] + (if (bnt/paid? claim) (if (= user-login winner-login) [:span "Received payout"] - [:span "No payout"]) - (if merged? "Merged" "Open"))] - ;; [:span.f5 (human-time updated)] + [:span "No payout"]))] [:div "Submitted a claim via " [:a {:href (pr-url claim)} - (str (:repo_owner claim) "/" (:repo_name claim) " PR #" (:pr_number claim))]] - (when (and merged? (not paid?)) - [:button.mt2.f5.outline-0.bg-sob-blue.white.pv2.ph3.pg-med.br2.bn - (merge (if (and merged? (not paid?)) - {} - {:disabled true}) - {:on-click #(rf/dispatch [:confirm-payout claim])} - (when (and (or confirming? bot-confirm-unmined?) - merged?) - {:class "busy loading" :disabled true})) - (if paid? - "Signed off" - "Confirm")])]]]])) + (str (:repo_owner claim) "/" (:repo_name claim) " PR #" (:pr_number claim))]]]] + (when render-view-claim-button? + [:div.dtc.v-mid + [:div.w-100 + [:a.f5.outline-0.bg-sob-blue.white.pv2.ph3.pg-med.br2.bn.pointer.hover-white + {:href (pr-url claim)} + "View Claim"]]])]])) - -(defn claim-list [bounties] +#_(defn claim-list [bounties] ;; TODO: exclude bounties with no claims (if (empty? bounties) [:div.ui.text "No items"] @@ -119,17 +134,51 @@ [claim-card bounty claim]) [:div.f4.muted-blue "No claims yet."])]])))) +(defn to-confirm-list [bounties] + (if (empty? bounties) + [:div.ui.text "No items"] + (into [:div] + (for [bounty bounties + :let [winning-claim (first (:claims bounty))]] ; TODO identify winning claim + ^{:key (:issue_id bounty)} + [:div.mb2 + [:div.pa3.bg-white.bb.b--light-gray + [bounty-card bounty]] + [:div.pa3.bg-white + [claim-card bounty winning-claim]] + [:div.pa3.bg-near-white + [confirm-row bounty winning-claim]]])))) + +(defn to-merge-list [bounties] + (if (empty? bounties) + [:div.ui.text "No items"] + (into [:div] + (for [bounty bounties + :let [claims (:claims bounty)]] ; TODO identify winning claim + ^{:key (:issue_id bounty)} + [:div.mb2 + [:div.pa3.bg-white.bb.b--light-gray + [bounty-card bounty] + [:div.mt3 [bounty-balance bounty]]] + [:div.pa3.bg-white + (for [claim (:claims bounty)] + ^{:key (:pr_id claim)} + [claim-card bounty claim {:render-view-claim-button? true}])]])))) + (defn bounty-stats [{:keys [paid unpaid]}] [:div.cf - [:div.fl-ns.w-50-ns.tc.pv4 - [:div.ttu.tracked "Open"] - [:div.f2.pa2 (common/usd-string (:combined-usd-value unpaid))] - [:div (:count unpaid) " bounties"]] + [:div.fl-ns.w-33-ns.tc.pv4 + #_[:div.ttu.tracked "Paid"] + [:div.f3.pa2 (common/usd-string (:combined-usd-value paid))] + [:div.ph4 "Invested so far"]] - [:div.fl-ns.w-50-ns.tc.pv4 - [:div.ttu.tracked "Paid"] - [:div.f2.pa2 (common/usd-string (:combined-usd-value paid))] - [:div (:count paid) " bounties"]]]) + [:div.fl-ns.w-33-ns.tc.pv4 + [:div.f3.pa2 (:count paid)] + [:div.ph4 "Bounties solved by contributors"]] + + [:div.fl-ns.w-33-ns.tc.pv4 + [:div.f3.pa2 (:count unpaid)] + [:div.ph4 "Open bounties in total"]]]) (def state-mapping {:opened :open @@ -141,14 +190,55 @@ :pending-maintainer-confirmation :pending-maintainer-confirmation :paid :paid}) +(defn bounty-title-link [bounty] + [:a {:href (common/issue-url (:repo-owner bounty) (:repo-name bounty) (:issue-number bounty))} + [:div.w-100.overflow-hidden + [:span.db.f5.pg-med.muted-blue.hover-black (:issue-title bounty)] + [:span.f6.gray.pg-book + {:on-click #(do (.preventDefault %) (prn (dissoc bounty :claims)))} + (common/human-time (:updated bounty))]]]) + +(defn unclaimed-bounty [bounty] + [:div.w-third-ns.fl.pa2 + [:div.bg-white.br2.br--top.pa2.h4 + [bounty-title-link bounty]] + [:div.bg-white.pa2.f7.br2.br--bottom + [bounty-balance bounty]]]) + +(defn paid-bounty [bounty] + [:div.w-third-ns.fl.pa2 + [:div.bg-white.br2.br--top.pa2.h4 + [bounty-title-link bounty]] + [:div.bg-white.ph2.f6 + "Paid out to @" (:winner_login bounty)] + [:div.bg-white.pa2.f7.br2.br--bottom + [bounty-balance bounty]]]) + +(defn expandable-bounty-list [bounty-component bounties] + (let [expanded? (r/atom false)] + (fn expandable-bounty-list-render [bounty-component bounties] + [:div + [:div.cf.nl2.nr2 + (for [bounty (cond->> bounties + (not @expanded?) (take 3))] + [bounty-component bounty])] + [:div.tr + [:span.f5.sob-blue.pointer + {:role "button" + :on-click #(reset! expanded? (not @expanded?))} + (if @expanded? + "Collapse ↑" + "See all ↓")]]]))) + (defn manage-payouts-page [] - (let [owner-bounties (rf/subscribe [:owner-bounties]) + (let [page (rf/subscribe [:page]) ; TODO fix this to subscribe to route subscription + owner-bounties (rf/subscribe [:owner-bounties]) bounty-stats-data (rf/subscribe [:owner-bounties-stats]) owner-bounties-loading? (rf/subscribe [:get-in [:owner-bounties-loading?]])] (fn [] (if @owner-bounties-loading? - [:container - [:div.ui.active.inverted.dimmer + [:div.pa5 + [:div.ui.active.inverted.dimmer.bg-none [:div.ui.text.loader "Loading"]]] (let [bounties (vals @owner-bounties) grouped (group-by (comp state-mapping :state) bounties)] @@ -158,12 +248,51 @@ [:i.warning.icon] "To sign off claims, please view Status Open Bounty in Status, Mist or Metamask"]) [bounty-stats @bounty-stats-data] - [:div.cf + [:div.cf.ba.b--white.mb2 + [:div.f4.fl.w-50-ns.pa4.tc + {:role "button" + :class (if (= @page :issuer-dashboard/to-confirm) "bg-white" "bg-near-white pointer") + :on-click #(routes/nav! :issuer-dashboard/to-confirm)} + "To confirm payment (" (count (get grouped :pending-maintainer-confirmation)) ")"] + [:div.f4.fl.w-50-ns.pa4.tc.pointer.bl.b--white + {:role "button" + :class (if (= @page :issuer-dashboard/to-merge) "bg-white" "bg-near-white pointer") + :on-click #(routes/nav! :issuer-dashboard/to-merge)} + "To merge (" (count (get grouped :claimed)) ")"]] + [:div + (case @page + :issuer-dashboard/to-confirm (to-confirm-list (get grouped :pending-maintainer-confirmation)) + :issuer-dashboard/to-merge (to-merge-list (get grouped :claimed)) + (cond + (seq (get grouped :pending-maintainer-confirmation)) + (routes/nav! :issuer-dashboard/to-confirm) + + (seq (get grouped :claimed)) + (routes/nav! :issuer-dashboard/to-merge) + + :else (routes/nav! :issuer-dashboard/to-confirm)))] + [:div.mt4 + [:h4.f3.sob-muted-blue "Bounties not claimed yet (" (count (get grouped :funded)) ")"] + [expandable-bounty-list + unclaimed-bounty + (reverse (sort-by :updated (get grouped :funded)))]] + + [:div.mt4 + [:h4.f3.sob-muted-blue "Paid out bounties (" (count (get grouped :paid)) ")"] + [expandable-bounty-list paid-bounty (get grouped :paid)]] + + + #_[:div.mt4 + [:h4.f3 "Revoked bounties (" (count (get grouped :paid)) ")"] + [expandable-bounty-list unclaimed-bounty (get grouped :paid)]] + + [:div.mb5] + #_[:div.cf [:button.pa2.tl.bn.bg-white.muted-blue {:on-click #(routes/nav! :issuer-dashboard/paid)} [:span.f4 "Paid"] [:br] (count (get grouped :paid)) " bounties"]] - (for [[k v] grouped] + #_(for [[k v] grouped] [:div {:key (name k)} [:h3 (name k) " — " (count v)]