18638- [Communities] Implement Permissions Drawer (#18988)

* Community Permissions Sheet

* Request to join screen: join as role

---------

Co-authored-by: Ajay Sivan <ajayesivan@gmail.com>
This commit is contained in:
Flavio Fraschetti 2024-03-21 10:18:50 -03:00 committed by GitHub
parent 90913500b4
commit 1e37765197
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 285 additions and 45 deletions

View File

@ -8,6 +8,7 @@
[status-im.contexts.communities.actions.addresses-for-permissions.view :as addresses-for-permissions] [status-im.contexts.communities.actions.addresses-for-permissions.view :as addresses-for-permissions]
[status-im.contexts.communities.actions.airdrop-addresses.view :as airdrop-addresses] [status-im.contexts.communities.actions.airdrop-addresses.view :as airdrop-addresses]
[status-im.contexts.communities.actions.community-rules.view :as community-rules] [status-im.contexts.communities.actions.community-rules.view :as community-rules]
[status-im.contexts.communities.actions.permissions-sheet.view :as permissions-sheet]
[status-im.contexts.communities.utils :as communities.utils] [status-im.contexts.communities.utils :as communities.utils]
[utils.i18n :as i18n] [utils.i18n :as i18n]
[utils.re-frame :as rf])) [utils.re-frame :as rf]))
@ -16,6 +17,7 @@
[] []
(let [{id :community-id} (rf/sub [:get-screen-params]) (let [{id :community-id} (rf/sub [:get-screen-params])
{:keys [name color images joined]} (rf/sub [:communities/community id]) {:keys [name color images joined]} (rf/sub [:communities/community id])
has-permissions? (rf/sub [:communities/has-permissions? id])
airdrop-account (rf/sub [:communities/airdrop-account id]) airdrop-account (rf/sub [:communities/airdrop-account id])
revealed-accounts (rf/sub [:communities/accounts-to-reveal id]) revealed-accounts (rf/sub [:communities/accounts-to-reveal id])
{:keys [highest-permission-role]} (rf/sub [:community/token-gated-overview id]) {:keys [highest-permission-role]} (rf/sub [:community/token-gated-overview id])
@ -55,17 +57,28 @@
:on-press (fn [password] :on-press (fn [password]
(rf/dispatch [:communities/request-to-join-with-addresses (rf/dispatch [:communities/request-to-join-with-addresses
{:community-id id :password password}]))}]) {:community-id id :password password}]))}])
(navigate-back)))] (navigate-back)))
open-permission-sheet
(rn/use-callback (fn []
(rf/dispatch [:show-bottom-sheet
{:content (fn [] [permissions-sheet/view id])}]))
[id])]
(rn/use-mount (rn/use-mount
(fn [] (fn []
(rf/dispatch [:communities/initialize-permission-addresses id]))) (rf/dispatch [:communities/initialize-permission-addresses id])))
[rn/safe-area-view {:style style/container} [rn/safe-area-view {:style style/container}
[quo/page-nav [quo/page-nav
{:text-align :left (cond-> {:text-align :left
:icon-name :i/close :icon-name :i/close
:on-press navigate-back :on-press navigate-back
:accessibility-label :back-button}] :accessibility-label :back-button}
has-permissions?
(assoc :right-side
[{:icon-left :i/unlocked
:on-press open-permission-sheet
:label (i18n/label :t/permissions)}]))]
[quo/page-top [quo/page-top
{:title (if can-edit-addresses? {:title (if can-edit-addresses?
(i18n/label :t/edit-shared-addresses) (i18n/label :t/edit-shared-addresses)
@ -88,7 +101,7 @@
{:list-type :settings {:list-type :settings
:data [{:title (if joined :data [{:title (if joined
(i18n/label :t/you-are-a-role {:role highest-role-text}) (i18n/label :t/you-are-a-role {:role highest-role-text})
(i18n/label :t/join-as-a {:role highest-role-text})) (i18n/label :t/join-as {:role highest-role-text}))
:on-press show-addresses-for-permissions :on-press show-addresses-for-permissions
:description :text :description :text
:action :arrow :action :arrow

View File

@ -4,10 +4,10 @@
[quo.core :as quo] [quo.core :as quo]
[react-native.core :as rn] [react-native.core :as rn]
[react-native.gesture :as gesture] [react-native.gesture :as gesture]
[status-im.common.not-implemented :as not-implemented]
[status-im.common.password-authentication.view :as password-authentication] [status-im.common.password-authentication.view :as password-authentication]
[status-im.constants :as constants] [status-im.constants :as constants]
[status-im.contexts.communities.actions.addresses-for-permissions.style :as style] [status-im.contexts.communities.actions.addresses-for-permissions.style :as style]
[status-im.contexts.communities.actions.permissions-sheet.view :as permissions-sheet]
[utils.i18n :as i18n] [utils.i18n :as i18n]
[utils.money :as money] [utils.money :as money]
[utils.re-frame :as rf])) [utils.re-frame :as rf]))
@ -151,14 +151,23 @@
(defn- page-top (defn- page-top
[{:keys [community-id identical-choices? can-edit-addresses?]}] [{:keys [community-id identical-choices? can-edit-addresses?]}]
(let [{:keys [name logo color]} (rf/sub [:communities/for-context-tag community-id]) (let [{:keys [name logo color]} (rf/sub [:communities/for-context-tag community-id])
confirm-discard-changes (rn/use-callback has-permissions? (rf/sub [:communities/has-permissions? community-id])
(fn [] confirm-discard-changes
(if identical-choices? (rn/use-callback
(rf/dispatch [:dismiss-modal :addresses-for-permissions]) (fn []
(rf/dispatch [:show-bottom-sheet (if identical-choices?
{:content (fn [] [confirm-discard-drawer (rf/dispatch [:dismiss-modal :addresses-for-permissions])
community-id])}]))) (rf/dispatch [:show-bottom-sheet
[identical-choices?])] {:content (fn [] [confirm-discard-drawer
community-id])}])))
[identical-choices? community-id])
open-permission-sheet
(rn/use-callback (fn []
(rf/dispatch [:show-bottom-sheet
{:content (fn [] [permissions-sheet/view
community-id])}]))
[community-id])]
[:<> [:<>
(when can-edit-addresses? (when can-edit-addresses?
[quo/page-nav [quo/page-nav
@ -175,15 +184,16 @@
:community-logo logo :community-logo logo
:community-name name}}] :community-name name}}]
[quo/drawer-top [quo/drawer-top
{:type :context-tag (cond-> {:type :context-tag
:title (i18n/label :t/addresses-for-permissions) :title (i18n/label :t/addresses-for-permissions)
:context-tag-type :community :context-tag-type :community
:community-name name :community-name name
:button-icon :i/info :community-logo logo
:button-type :grey :customization-color color}
:on-button-press not-implemented/alert has-permissions?
:community-logo logo (assoc :button-icon :i/info
:customization-color color}])])) :button-type :grey
:on-button-press open-permission-sheet))])]))
(defn view (defn view
[] []

View File

@ -0,0 +1,5 @@
(ns status-im.contexts.communities.actions.permissions-sheet.style)
(def container
{:flex 1
:padding-horizontal 20})

View File

@ -0,0 +1,38 @@
(ns status-im.contexts.communities.actions.permissions-sheet.view
(:require
[quo.core :as quo]
[react-native.core :as rn]
[react-native.gesture :as gesture]
[status-im.contexts.communities.actions.permissions-sheet.style :as style]
[status-im.contexts.communities.utils :as communities.utils]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn- role-text
[role]
(i18n/label
(communities.utils/role->translation-key role
:t/token-master)))
(defn- permission-view
[{:keys [tokens role satisfied?]}]
(when (seq tokens)
^{:key role}
[rn/view {:style {:margin-bottom 20}}
[quo/text {:weight :medium}
(if satisfied?
(i18n/label :t/you-eligible-to-join-as {:role (role-text role)})
(i18n/label :t/you-not-eligible-to-join-as {:role (role-text role)}))]
[quo/text {:size :paragraph-2 :style {:padding-bottom 8}}
(if satisfied?
(i18n/label :t/you-hodl)
(i18n/label :t/you-must-hold))]
[quo/token-requirement-list
{:tokens tokens
:container-style {:padding-horizontal 20}}]]))
(defn view
[id]
(let [permissions (rf/sub [:community/token-permissions id])]
[gesture/scroll-view {:style style/container}
(map permission-view permissions)]))

View File

@ -30,7 +30,9 @@
{:db (assoc-in db {:db (assoc-in db
[:communities id] [:communities id]
(assoc community :last-opened-at (max last-opened-at previous-last-opened-at))) (assoc community :last-opened-at (max last-opened-at previous-last-opened-at)))
:fx [(when (not joined) :fx [[:dispatch
[:communities/check-permissions-to-join-community-with-all-addresses id]]
(when (not joined)
[:dispatch [:chat.ui/spectate-community id]]) [:dispatch [:chat.ui/spectate-community id]])
(when (nil? token-permissions-check) (when (nil? token-permissions-check)
[:dispatch [:communities/check-permissions-to-join-community id]])]})))) [:dispatch [:communities/check-permissions-to-join-community id]])]}))))

View File

@ -195,7 +195,9 @@
(is (match? community-id (is (match? community-id
(-> effects :db :communities (get community-id) :id))) (-> effects :db :communities (get community-id) :id)))
(is (match? (is (match?
[[:dispatch [:chat.ui/spectate-community community-id]] [[:dispatch
[:communities/check-permissions-to-join-community-with-all-addresses community-id]]
[:dispatch [:chat.ui/spectate-community community-id]]
[:dispatch [:communities/check-permissions-to-join-community community-id]]] [:dispatch [:communities/check-permissions-to-join-community community-id]]]
(filter some? (:fx effects)))))) (filter some? (:fx effects))))))
@ -203,14 +205,18 @@
(let [community (assoc community :joined true) (let [community (assoc community :joined true)
effects (events/handle-community {} [community])] effects (events/handle-community {} [community])]
(is (match? (is (match?
[[:dispatch [:communities/check-permissions-to-join-community community-id]]] [[:dispatch
[:communities/check-permissions-to-join-community-with-all-addresses community-id]]
[:dispatch [:communities/check-permissions-to-join-community community-id]]]
(filter some? (:fx effects)))))) (filter some? (:fx effects))))))
(testing "given a community with token-permissions-check" (testing "given a community with token-permissions-check"
(let [community (assoc community :token-permissions-check :fake-token-permissions-check) (let [community (assoc community :token-permissions-check :fake-token-permissions-check)
effects (events/handle-community {} [community])] effects (events/handle-community {} [community])]
(is (match? (is (match?
[[:dispatch [:chat.ui/spectate-community community-id]]] [[:dispatch
[:communities/check-permissions-to-join-community-with-all-addresses community-id]]
[:dispatch [:chat.ui/spectate-community community-id]]]
(filter some? (:fx effects)))))) (filter some? (:fx effects))))))
(testing "given a community with lower clock" (testing "given a community with lower clock"
(let [effects (events/handle-community {:db {:communities {community-id {:clock 3}}}} [community])] (let [effects (events/handle-community {:db {:communities {community-id {:clock 3}}}} [community])]

View File

@ -1,5 +1,6 @@
(ns status-im.contexts.communities.overview.events (ns status-im.contexts.communities.overview.events
(:require (:require
[status-im.contexts.communities.utils :as utils]
[taoensso.timbre :as log] [taoensso.timbre :as log]
[utils.i18n :as i18n] [utils.i18n :as i18n]
[utils.re-frame :as rf])) [utils.re-frame :as rf]))
@ -36,6 +37,38 @@
community-id community-id
err))}]})))) err))}]}))))
(rf/reg-event-fx :communities/check-permissions-to-join-community-with-all-addresses-success
(fn [{:keys [db]} [community-id result]]
{:db (assoc-in db
[:communities/permissions-check-all community-id]
{:checking? false
:check result})}))
(rf/reg-event-fx :communities/check-permissions-to-join-community-with-all-addresses-failed
(fn [{:keys [db]} [community-id]]
{:db (assoc-in db [:communities/permissions-check-all community-id :checking?] false)}))
(rf/reg-event-fx :communities/check-permissions-to-join-community-with-all-addresses
(fn [{:keys [db]} [community-id]]
(let [accounts (utils/sorted-non-watch-only-accounts db)
addresses (set (map :address accounts))]
{:db (assoc-in db [:communities/permissions-check community-id :checking?] true)
:json-rpc/call [{:method "wakuext_checkPermissionsToJoinCommunity"
:params [(cond-> {:communityId community-id}
(seq addresses)
(assoc :addresses addresses))]
:on-success
[:communities/check-permissions-to-join-community-with-all-addresses-success
community-id]
:on-error
(fn [err]
(rf/dispatch
[:communities/check-permissions-to-join-community-with-all-addresses-failed
community-id])
(log/error "failed to check permissions for all addresses"
community-id
err))}]})))
(defn request-to-join (defn request-to-join
[{:keys [db]} [{:keys [db]}
[{:keys [community-id password]}]] [{:keys [community-id password]}]]

View File

@ -3,6 +3,7 @@
[oops.core :as oops] [oops.core :as oops]
[quo.core :as quo] [quo.core :as quo]
[quo.foundations.colors :as colors] [quo.foundations.colors :as colors]
[quo.theme :as theme]
[react-native.blur :as blur] [react-native.blur :as blur]
[react-native.core :as rn] [react-native.core :as rn]
[reagent.core :as reagent] [reagent.core :as reagent]
@ -97,20 +98,21 @@
(defn- info-button (defn- info-button
[] []
[rn/pressable (let [theme (theme/use-theme)]
{:on-press [rn/pressable
#(rf/dispatch {:on-press
[:show-bottom-sheet #(rf/dispatch
{:content [:show-bottom-sheet
(fn [] {:content
[quo/documentation-drawers (fn []
{:title (i18n/label :t/token-gated-communities) [quo/documentation-drawers
:show-button? true {:title (i18n/label :t/token-gated-communities)
:button-label (i18n/label :t/read-more) :show-button? true
:button-icon :info} :button-label (i18n/label :t/read-more)
[quo/text (i18n/label :t/token-gated-communities-info)]])}])} :button-icon :info}
[rn/view [quo/text (i18n/label :t/token-gated-communities-info)]])}])}
[quo/icon :i/info {:no-color true}]]]) [rn/view
[quo/icon :i/info {:color (colors/theme-colors colors/neutral-50 colors/neutral-40 theme)}]]]))
(defn- network-not-supported (defn- network-not-supported
[] []

View File

@ -266,8 +266,8 @@
:position position :position position
:mentions-count (or unviewed-mentions-count 0) :mentions-count (or unviewed-mentions-count 0)
:can-post? can-post? :can-post? can-post?
;; NOTE: this is a troolean nil->no permissions, true->no access, false -> ;; NOTE: this is a troolean nil->no permissions, true->no access, false
;; has access ;; -> has access
:locked? (when token-gated? :locked? (when token-gated?
(and (not can-view?) (and (not can-view?)
(not can-post?))) (not can-post?)))
@ -320,6 +320,12 @@
(fn [permissions [_ id]] (fn [permissions [_ id]]
(get permissions id))) (get permissions id)))
(re-frame/reg-sub
:communities/checking-permissions-all-by-id
:<- [:communities/permissions-check-all]
(fn [permissions [_ id]]
(get permissions id)))
(re-frame/reg-sub (re-frame/reg-sub
:community/token-gated-overview :community/token-gated-overview
(fn [[_ community-id]] (fn [[_ community-id]]
@ -368,3 +374,35 @@
(map (fn [{sym :symbol image :image}] (map (fn [{sym :symbol image :image}]
{sym image})) {sym image}))
(into {})))) (into {}))))
(re-frame/reg-sub
:community/token-permissions
(fn [[_ community-id]]
[(re-frame/subscribe [:communities/community community-id])
(re-frame/subscribe [:communities/checking-permissions-all-by-id community-id])])
(fn [[{:keys [token-images]}
{:keys [checking? check]}] _]
(let [roles (:roles check)
member-and-satisifed-roles (filter #(or (= (:type %)
constants/community-token-permission-become-member)
(:satisfied %))
roles)]
(mapv (fn [role]
{:role (:type role)
:satisfied? (:satisfied role)
:tokens (map (fn [{:keys [tokenRequirement]}]
(map
(partial token-requirement->token
checking?
token-images)
tokenRequirement))
(:criteria role))})
member-and-satisifed-roles))))
(re-frame/reg-sub
:communities/has-permissions?
(fn [[_ community-id]]
[(re-frame/subscribe [:community/token-permissions community-id])])
(fn [[permissions] _]
(let [all-tokens (apply concat (map :tokens permissions))]
(boolean (some seq all-tokens)))))

View File

@ -387,3 +387,94 @@
(is (match? {"DOGE" "data:image/jpeg;base64,/9j/2wCEAAYEBQYFBAYGBQYH" (is (match? {"DOGE" "data:image/jpeg;base64,/9j/2wCEAAYEBQYFBAYGBQYH"
"BTC" "data:image/jpeg;base64,/9j/2wCEAAYEBQYFBAYGBQYH"} "BTC" "data:image/jpeg;base64,/9j/2wCEAAYEBQYFBAYGBQYH"}
(rf/sub [sub-name community-id]))))) (rf/sub [sub-name community-id])))))
(h/deftest-sub :community/token-permissions
[sub-name]
(testing
"with visible permissions"
(let
[checking? false
token-image-eth "data:image/jpeg;base64,/9j/2w"
checks
{:checking? checking?
:check
{:roles
[{:type 1
:satisfied true
:criteria [{:roles 1
:tokenRequirement [{:satisfied true
:criteria {:contract_addresses
{:421614
0x0000000000000000000000000000000000000000
:11155111
0x0000000000000000000000000000000000000000
:11155420
0x0000000000000000000000000000000000000000}
:type 1
:symbol "ETH"
:amount 1
:decimals 18
:amountInWei 1000000000000000000}}]
:criteria [true]}]}
{:type 2
:satisfied false
:criteria [{:roles 2
:tokenRequirement [{:satisfied false
:criteria {:contract_addresses
{:11155111
0x3e622317f8c93f7328350cf0b56d9ed4c620c5d6}
:type 1
:symbol "DAI"
:amount 10
:decimals 18
:amountInWei 10000000000000000000}}]
:criteria [false]}]}]}
}
community {:id community-id
:checking-permissions? checking?
:token-images {"ETH" token-image-eth}
:name "Community super name"
:can-request-access? false
:outroMessage "bla"
:verified false}]
(swap! rf-db/app-db assoc-in [:communities community-id] community)
(swap! rf-db/app-db assoc-in [:communities/permissions-check-all community-id] checks)
(is
(match? [{:role 1
:satisfied? true
:tokens
[[{:symbol "ETH"
:sufficient? true
:loading? false
:amount "1"
:img-src token-image-eth}]]}
{:role 2
:satisfied? false
:tokens [[{:symbol "DAI"
:sufficient? false
:loading? false
:amount "10"
:img-src nil}]]}]
(rf/sub [sub-name community-id])))))
(testing
"without any visible permissions"
(let
[checking? false
token-image-eth "data:image/jpeg;base64,/9j/2w"
checks
{:checking? checking?
:check
{:roles []}
}
community {:id community-id
:checking-permissions? checking?
:token-images {"ETH" token-image-eth}
:name "Community super name"
:can-request-access? false
:outroMessage "bla"
:verified false}]
(swap! rf-db/app-db assoc-in [:communities community-id] community)
(swap! rf-db/app-db assoc-in [:communities/permissions-check-all community-id] checks)
(is
(match? []
(rf/sub [sub-name community-id]))))))

View File

@ -149,6 +149,7 @@
(reg-root-key-sub :contract-communities :contract-communities) (reg-root-key-sub :contract-communities :contract-communities)
(reg-root-key-sub :communities/permissioned-balances :communities/permissioned-balances) (reg-root-key-sub :communities/permissioned-balances :communities/permissioned-balances)
(reg-root-key-sub :communities/permissions-check :communities/permissions-check) (reg-root-key-sub :communities/permissions-check :communities/permissions-check)
(reg-root-key-sub :communities/permissions-check-all :communities/permissions-check-all)
(reg-root-key-sub :communities/all-addresses-to-reveal :communities/all-addresses-to-reveal) (reg-root-key-sub :communities/all-addresses-to-reveal :communities/all-addresses-to-reveal)
(reg-root-key-sub :communities/all-airdrop-addresses :communities/all-airdrop-addresses) (reg-root-key-sub :communities/all-airdrop-addresses :communities/all-airdrop-addresses)
(reg-root-key-sub :communities/selected-share-all-addresses :communities/selected-share-all-addresses) (reg-root-key-sub :communities/selected-share-all-addresses :communities/selected-share-all-addresses)

View File

@ -191,7 +191,7 @@
"no-addresses-selected": "At least 1 address must be shared with community", "no-addresses-selected": "At least 1 address must be shared with community",
"confirm-changes": "Confirm changes", "confirm-changes": "Confirm changes",
"airdrop-addresses": "Address for airdrops", "airdrop-addresses": "Address for airdrops",
"join-as-a": "Join as a {{role}}", "join-as": "Join as {{role}}",
"all-addresses": "All addresses", "all-addresses": "All addresses",
"for-airdrops": "For airdrops", "for-airdrops": "For airdrops",
"members-label": "Members", "members-label": "Members",
@ -2297,6 +2297,7 @@
"you-eligible-to-join-as": "Youre eligible to join as {{role}}", "you-eligible-to-join-as": "Youre eligible to join as {{role}}",
"eligible-to-join-as": "Eligible to join as", "eligible-to-join-as": "Eligible to join as",
"you-not-eligible-to-join": "Youre not eligible to join", "you-not-eligible-to-join": "Youre not eligible to join",
"you-not-eligible-to-join-as": "Youre not eligible to join as {{role}}",
"you-hold-number-of-hold-tokens-of-these": "You hold {{number-of-hold-tokens}} of these:", "you-hold-number-of-hold-tokens-of-these": "You hold {{number-of-hold-tokens}} of these:",
"addresses-dont-contain-tokens-needed": "These addresses dont contain tokens needed to join", "addresses-dont-contain-tokens-needed": "These addresses dont contain tokens needed to join",
"you-hodl": "You hodl:", "you-hodl": "You hodl:",