From 1e37765197d99d5b0d8fd11caa444c6ee1b52172 Mon Sep 17 00:00:00 2001 From: Flavio Fraschetti Date: Thu, 21 Mar 2024 10:18:50 -0300 Subject: [PATCH] 18638- [Communities] Implement Permissions Drawer (#18988) * Community Permissions Sheet * Request to join screen: join as role --------- Co-authored-by: Ajay Sivan --- .../actions/accounts_selection/view.cljs | 25 +++-- .../addresses_for_permissions/view.cljs | 46 ++++++---- .../actions/permissions_sheet/style.cljs | 5 + .../actions/permissions_sheet/view.cljs | 38 ++++++++ .../contexts/communities/events.cljs | 4 +- .../contexts/communities/events_test.cljs | 12 ++- .../contexts/communities/overview/events.cljs | 33 +++++++ .../contexts/communities/overview/view.cljs | 30 +++--- src/status_im/subs/communities.cljs | 42 ++++++++- src/status_im/subs/communities_test.cljs | 91 +++++++++++++++++++ src/status_im/subs/root.cljs | 1 + translations/en.json | 3 +- 12 files changed, 285 insertions(+), 45 deletions(-) create mode 100644 src/status_im/contexts/communities/actions/permissions_sheet/style.cljs create mode 100644 src/status_im/contexts/communities/actions/permissions_sheet/view.cljs diff --git a/src/status_im/contexts/communities/actions/accounts_selection/view.cljs b/src/status_im/contexts/communities/actions/accounts_selection/view.cljs index 16fc2ec2c6..bf3d5e323b 100644 --- a/src/status_im/contexts/communities/actions/accounts_selection/view.cljs +++ b/src/status_im/contexts/communities/actions/accounts_selection/view.cljs @@ -8,6 +8,7 @@ [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.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] [utils.i18n :as i18n] [utils.re-frame :as rf])) @@ -16,6 +17,7 @@ [] (let [{id :community-id} (rf/sub [:get-screen-params]) {: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]) revealed-accounts (rf/sub [:communities/accounts-to-reveal id]) {:keys [highest-permission-role]} (rf/sub [:community/token-gated-overview id]) @@ -55,17 +57,28 @@ :on-press (fn [password] (rf/dispatch [:communities/request-to-join-with-addresses {: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 (fn [] (rf/dispatch [:communities/initialize-permission-addresses id]))) [rn/safe-area-view {:style style/container} [quo/page-nav - {:text-align :left - :icon-name :i/close - :on-press navigate-back - :accessibility-label :back-button}] + (cond-> {:text-align :left + :icon-name :i/close + :on-press navigate-back + :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 {:title (if can-edit-addresses? (i18n/label :t/edit-shared-addresses) @@ -88,7 +101,7 @@ {:list-type :settings :data [{:title (if joined (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 :description :text :action :arrow diff --git a/src/status_im/contexts/communities/actions/addresses_for_permissions/view.cljs b/src/status_im/contexts/communities/actions/addresses_for_permissions/view.cljs index 042d04a346..e34c942e93 100644 --- a/src/status_im/contexts/communities/actions/addresses_for_permissions/view.cljs +++ b/src/status_im/contexts/communities/actions/addresses_for_permissions/view.cljs @@ -4,10 +4,10 @@ [quo.core :as quo] [react-native.core :as rn] [react-native.gesture :as gesture] - [status-im.common.not-implemented :as not-implemented] [status-im.common.password-authentication.view :as password-authentication] [status-im.constants :as constants] [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.money :as money] [utils.re-frame :as rf])) @@ -151,14 +151,23 @@ (defn- page-top [{:keys [community-id identical-choices? can-edit-addresses?]}] (let [{:keys [name logo color]} (rf/sub [:communities/for-context-tag community-id]) - confirm-discard-changes (rn/use-callback - (fn [] - (if identical-choices? - (rf/dispatch [:dismiss-modal :addresses-for-permissions]) - (rf/dispatch [:show-bottom-sheet - {:content (fn [] [confirm-discard-drawer - community-id])}]))) - [identical-choices?])] + has-permissions? (rf/sub [:communities/has-permissions? community-id]) + confirm-discard-changes + (rn/use-callback + (fn [] + (if identical-choices? + (rf/dispatch [:dismiss-modal :addresses-for-permissions]) + (rf/dispatch [:show-bottom-sheet + {: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? [quo/page-nav @@ -175,15 +184,16 @@ :community-logo logo :community-name name}}] [quo/drawer-top - {:type :context-tag - :title (i18n/label :t/addresses-for-permissions) - :context-tag-type :community - :community-name name - :button-icon :i/info - :button-type :grey - :on-button-press not-implemented/alert - :community-logo logo - :customization-color color}])])) + (cond-> {:type :context-tag + :title (i18n/label :t/addresses-for-permissions) + :context-tag-type :community + :community-name name + :community-logo logo + :customization-color color} + has-permissions? + (assoc :button-icon :i/info + :button-type :grey + :on-button-press open-permission-sheet))])])) (defn view [] diff --git a/src/status_im/contexts/communities/actions/permissions_sheet/style.cljs b/src/status_im/contexts/communities/actions/permissions_sheet/style.cljs new file mode 100644 index 0000000000..0f2ccda313 --- /dev/null +++ b/src/status_im/contexts/communities/actions/permissions_sheet/style.cljs @@ -0,0 +1,5 @@ +(ns status-im.contexts.communities.actions.permissions-sheet.style) + +(def container + {:flex 1 + :padding-horizontal 20}) diff --git a/src/status_im/contexts/communities/actions/permissions_sheet/view.cljs b/src/status_im/contexts/communities/actions/permissions_sheet/view.cljs new file mode 100644 index 0000000000..1ed0f42ae1 --- /dev/null +++ b/src/status_im/contexts/communities/actions/permissions_sheet/view.cljs @@ -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)])) diff --git a/src/status_im/contexts/communities/events.cljs b/src/status_im/contexts/communities/events.cljs index 023e58ff44..1a0b8aedd5 100644 --- a/src/status_im/contexts/communities/events.cljs +++ b/src/status_im/contexts/communities/events.cljs @@ -30,7 +30,9 @@ {:db (assoc-in db [:communities id] (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]]) (when (nil? token-permissions-check) [:dispatch [:communities/check-permissions-to-join-community id]])]})))) diff --git a/src/status_im/contexts/communities/events_test.cljs b/src/status_im/contexts/communities/events_test.cljs index ddf3a0a7f4..502d6bb71e 100644 --- a/src/status_im/contexts/communities/events_test.cljs +++ b/src/status_im/contexts/communities/events_test.cljs @@ -195,7 +195,9 @@ (is (match? community-id (-> effects :db :communities (get community-id) :id))) (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]]] (filter some? (:fx effects)))))) @@ -203,14 +205,18 @@ (let [community (assoc community :joined true) effects (events/handle-community {} [community])] (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)))))) (testing "given a community with token-permissions-check" (let [community (assoc community :token-permissions-check :fake-token-permissions-check) effects (events/handle-community {} [community])] (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)))))) (testing "given a community with lower clock" (let [effects (events/handle-community {:db {:communities {community-id {:clock 3}}}} [community])] diff --git a/src/status_im/contexts/communities/overview/events.cljs b/src/status_im/contexts/communities/overview/events.cljs index c0af93896a..638312d315 100644 --- a/src/status_im/contexts/communities/overview/events.cljs +++ b/src/status_im/contexts/communities/overview/events.cljs @@ -1,5 +1,6 @@ (ns status-im.contexts.communities.overview.events (:require + [status-im.contexts.communities.utils :as utils] [taoensso.timbre :as log] [utils.i18n :as i18n] [utils.re-frame :as rf])) @@ -36,6 +37,38 @@ community-id 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 [{:keys [db]} [{:keys [community-id password]}]] diff --git a/src/status_im/contexts/communities/overview/view.cljs b/src/status_im/contexts/communities/overview/view.cljs index 0ac7fefd1f..6d6695f207 100644 --- a/src/status_im/contexts/communities/overview/view.cljs +++ b/src/status_im/contexts/communities/overview/view.cljs @@ -3,6 +3,7 @@ [oops.core :as oops] [quo.core :as quo] [quo.foundations.colors :as colors] + [quo.theme :as theme] [react-native.blur :as blur] [react-native.core :as rn] [reagent.core :as reagent] @@ -97,20 +98,21 @@ (defn- info-button [] - [rn/pressable - {:on-press - #(rf/dispatch - [:show-bottom-sheet - {:content - (fn [] - [quo/documentation-drawers - {:title (i18n/label :t/token-gated-communities) - :show-button? true - :button-label (i18n/label :t/read-more) - :button-icon :info} - [quo/text (i18n/label :t/token-gated-communities-info)]])}])} - [rn/view - [quo/icon :i/info {:no-color true}]]]) + (let [theme (theme/use-theme)] + [rn/pressable + {:on-press + #(rf/dispatch + [:show-bottom-sheet + {:content + (fn [] + [quo/documentation-drawers + {:title (i18n/label :t/token-gated-communities) + :show-button? true + :button-label (i18n/label :t/read-more) + :button-icon :info} + [quo/text (i18n/label :t/token-gated-communities-info)]])}])} + [rn/view + [quo/icon :i/info {:color (colors/theme-colors colors/neutral-50 colors/neutral-40 theme)}]]])) (defn- network-not-supported [] diff --git a/src/status_im/subs/communities.cljs b/src/status_im/subs/communities.cljs index 9fcabd1034..82217a3777 100644 --- a/src/status_im/subs/communities.cljs +++ b/src/status_im/subs/communities.cljs @@ -266,8 +266,8 @@ :position position :mentions-count (or unviewed-mentions-count 0) :can-post? can-post? - ;; NOTE: this is a troolean nil->no permissions, true->no access, false -> - ;; has access + ;; NOTE: this is a troolean nil->no permissions, true->no access, false + ;; -> has access :locked? (when token-gated? (and (not can-view?) (not can-post?))) @@ -320,6 +320,12 @@ (fn [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 :community/token-gated-overview (fn [[_ community-id]] @@ -368,3 +374,35 @@ (map (fn [{sym :symbol image :image}] {sym image})) (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))))) diff --git a/src/status_im/subs/communities_test.cljs b/src/status_im/subs/communities_test.cljs index 48b131d659..dafc224503 100644 --- a/src/status_im/subs/communities_test.cljs +++ b/src/status_im/subs/communities_test.cljs @@ -387,3 +387,94 @@ (is (match? {"DOGE" "data:image/jpeg;base64,/9j/2wCEAAYEBQYFBAYGBQYH" "BTC" "data:image/jpeg;base64,/9j/2wCEAAYEBQYFBAYGBQYH"} (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])))))) diff --git a/src/status_im/subs/root.cljs b/src/status_im/subs/root.cljs index f6ae1dd653..079b6c5129 100644 --- a/src/status_im/subs/root.cljs +++ b/src/status_im/subs/root.cljs @@ -149,6 +149,7 @@ (reg-root-key-sub :contract-communities :contract-communities) (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-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-airdrop-addresses :communities/all-airdrop-addresses) (reg-root-key-sub :communities/selected-share-all-addresses :communities/selected-share-all-addresses) diff --git a/translations/en.json b/translations/en.json index 8fc44093ad..298368e6d6 100644 --- a/translations/en.json +++ b/translations/en.json @@ -191,7 +191,7 @@ "no-addresses-selected": "At least 1 address must be shared with community", "confirm-changes": "Confirm changes", "airdrop-addresses": "Address for airdrops", - "join-as-a": "Join as a {{role}}", + "join-as": "Join as {{role}}", "all-addresses": "All addresses", "for-airdrops": "For airdrops", "members-label": "Members", @@ -2297,6 +2297,7 @@ "you-eligible-to-join-as": "You’re eligible to join as {{role}}", "eligible-to-join-as": "Eligible to join as", "you-not-eligible-to-join": "You’re not eligible to join", + "you-not-eligible-to-join-as": "You’re not eligible to join as {{role}}", "you-hold-number-of-hold-tokens-of-these": "You hold {{number-of-hold-tokens}} of these:", "addresses-dont-contain-tokens-needed": "These addresses don’t contain tokens needed to join", "you-hodl": "You hodl:",