feat: show highest permission role when join community (#18497)

* feat: get revealed accounts for joined community

Signed-off-by: yqrashawn <namy.19@gmail.com>

* Show token requirements only when membership permissions are present.

- Add membership-permissions? to community state.

* feat: show highest permission role in overview and req to join

Signed-off-by: yqrashawn <namy.19@gmail.com>

* feat: get highest perm role in addr for perms

Signed-off-by: yqrashawn <namy.19@gmail.com>

* feat: ui for address for perm drawer

* test: unit test and spec for handle-community

Signed-off-by: yqrashawn <namy.19@gmail.com>

* fix: for #18581

* refactor: based on PR feedback

- move <-rpc to data store
- `role->translation-key`
- highest-permission-role-text -> highest-role-text
- filter out nils for handle community test

Signed-off-by: yqrashawn <namy.19@gmail.com>

---------

Signed-off-by: yqrashawn <namy.19@gmail.com>
Co-authored-by: Ajay Sivan <ajayesivan@gmail.com>
This commit is contained in:
yqrashawn 2024-01-26 15:38:10 +08:00 committed by GitHub
parent d82bf3365d
commit 1acf8f0d07
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 439 additions and 185 deletions

View File

@ -1,5 +1,8 @@
(ns legacy.status-im.data-store.communities (ns legacy.status-im.data-store.communities
(:require [clojure.set :as set])) (:require
[clojure.set :as set]
[clojure.walk :as walk]
[status-im.constants :as constants]))
(defn rpc->channel-permissions (defn rpc->channel-permissions
[rpc-channels-permissions] [rpc-channels-permissions]
@ -7,3 +10,66 @@
(fn [{:keys [viewAndPostPermissions viewOnlyPermissions]}] (fn [{:keys [viewAndPostPermissions viewOnlyPermissions]}]
{:view-only (set/rename-keys viewOnlyPermissions {:satisfied :satisfied?}) {:view-only (set/rename-keys viewOnlyPermissions {:satisfied :satisfied?})
:view-and-post (set/rename-keys viewAndPostPermissions {:satisfied :satisfied?})}))) :view-and-post (set/rename-keys viewAndPostPermissions {:satisfied :satisfied?})})))
(defn <-revealed-accounts-rpc
[accounts]
(mapv
#(set/rename-keys % {:isAirdropAddress :airdrop-address?})
(js->clj accounts :keywordize-keys true)))
(defn <-request-to-join-community-rpc
[r]
(set/rename-keys r
{:communityId :community-id
:publicKey :public-key
:chatId :chat-id}))
(defn <-requests-to-join-community-rpc
[requests key-fn]
(reduce #(assoc %1 (key-fn %2) (<-request-to-join-community-rpc %2)) {} requests))
(defn <-chats-rpc
[chats]
(reduce-kv (fn [acc k v]
(assoc acc
(name k)
(-> v
(assoc :can-post? (:canPost v))
(dissoc :canPost)
(update :members walk/stringify-keys))))
{}
chats))
(defn <-categories-rpc
[categ]
(reduce-kv #(assoc %1 (name %2) %3) {} categ))
(defn <-rpc
[c]
(-> c
(set/rename-keys {:canRequestAccess :can-request-access?
:canManageUsers :can-manage-users?
:canDeleteMessageForEveryone :can-delete-message-for-everyone?
:canJoin :can-join?
:requestedToJoinAt :requested-to-join-at
:isMember :is-member?
:adminSettings :admin-settings
:tokenPermissions :token-permissions
:communityTokensMetadata :tokens-metadata
:introMessage :intro-message
:muteTill :muted-till})
(update :admin-settings
set/rename-keys
{:pinMessageAllMembersEnabled :pin-message-all-members-enabled?})
(update :members walk/stringify-keys)
(update :chats <-chats-rpc)
(update :token-permissions seq)
(update :categories <-categories-rpc)
(assoc :membership-permissions?
(some #(= (:type %) constants/community-token-permission-become-member)
(vals (:tokenPermissions c))))
(assoc :token-images
(reduce (fn [acc {sym :symbol image :image}]
(assoc acc sym image))
{}
(:communityTokensMetadata c)))))

View File

@ -15,7 +15,7 @@
[:token {:optional true} [:or keyword? string?]] [:token {:optional true} [:or keyword? string?]]
[:style {:optional true} map?] [:style {:optional true} map?]
;; Ignores `token` and uses this as parameter to `rn/image`'s source. ;; Ignores `token` and uses this as parameter to `rn/image`'s source.
[:image-source {:optional true} [:or :schema.common/image-source :string]]]] [:image-source {:optional true} [:maybe [:or :schema.common/image-source :string]]]]]
:any]) :any])
(defn- size->number (defn- size->number

View File

@ -6,6 +6,7 @@
[legacy.status-im.chat.models.message :as models.message] [legacy.status-im.chat.models.message :as models.message]
[legacy.status-im.data-store.activities :as data-store.activities] [legacy.status-im.data-store.activities :as data-store.activities]
[legacy.status-im.data-store.chats :as data-store.chats] [legacy.status-im.data-store.chats :as data-store.chats]
[legacy.status-im.data-store.communities :as data-store.communities]
[legacy.status-im.data-store.invitations :as data-store.invitations] [legacy.status-im.data-store.invitations :as data-store.invitations]
[legacy.status-im.group-chats.core :as models.group] [legacy.status-im.group-chats.core :as models.group]
[legacy.status-im.multiaccounts.update.core :as update.core] [legacy.status-im.multiaccounts.update.core :as update.core]
@ -127,7 +128,7 @@
(seq requests-to-join-community) (seq requests-to-join-community)
(let [requests (->> requests-to-join-community (let [requests (->> requests-to-join-community
types/js->clj types/js->clj
(map communities/<-request-to-join-community-rpc))] (map data-store.communities/<-request-to-join-community-rpc))]
(js-delete response-js "requestsToJoinCommunity") (js-delete response-js "requestsToJoinCommunity")
(rf/merge cofx (rf/merge cofx
(process-next response-js sync-handler) (process-next response-js sync-handler)

View File

@ -6,25 +6,28 @@
[status-im.common.password-authentication.view :as password-authentication] [status-im.common.password-authentication.view :as password-authentication]
[status-im.contexts.communities.actions.accounts-selection.style :as style] [status-im.contexts.communities.actions.accounts-selection.style :as style]
[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.utils :as communities.utils]
[utils.i18n :as i18n] [utils.i18n :as i18n]
[utils.re-frame :as rf])) [utils.re-frame :as rf]))
(defn- join-community-and-navigate-back (defn- join-community-and-navigate-back
[id] [id]
(rf/dispatch [:password-authentication/show (rf/dispatch [:password-authentication/show {:content (fn [] [password-authentication/view])}
{:content (fn [] [password-authentication/view])}
{:label (i18n/label :t/join-open-community) {:label (i18n/label :t/join-open-community)
:on-press #(rf/dispatch [:communities/request-to-join-with-addresses :on-press #(rf/dispatch [:communities/request-to-join-with-addresses
{:community-id id {:community-id id :password %}])}])
:password %}])}])
(rf/dispatch [:navigate-back])) (rf/dispatch [:navigate-back]))
(defn f-view-internal (defn f-view-internal
[] []
(let [{id :community-id} (rf/sub [:get-screen-params]) (let [{id :community-id} (rf/sub [:get-screen-params])
{:keys [name color images]} (rf/sub [:communities/community id]) {:keys [name color images]} (rf/sub [:communities/community id])
airdrop-account (rf/sub [:communities/airdrop-account id]) airdrop-account (rf/sub [:communities/airdrop-account id])
selected-accounts (rf/sub [:communities/selected-permission-accounts id])] selected-accounts (rf/sub [:communities/selected-permission-accounts id])
{:keys [highest-permission-role]} (rf/sub [:community/token-gated-overview id])
highest-role-text (i18n/label (communities.utils/role->translation-key
highest-permission-role
:t/member))]
[rn/view {:style style/container} [rn/view {:style style/container}
[quo/page-nav [quo/page-nav
{:text-align :left {:text-align :left
@ -48,7 +51,7 @@
(i18n/label :t/address-to-share)] (i18n/label :t/address-to-share)]
[quo/category [quo/category
{:list-type :settings {:list-type :settings
:data [{:title (i18n/label :t/join-as-a-member) :data [{:title (i18n/label :t/join-as-a {:role highest-role-text})
:on-press #(rf/dispatch [:open-modal :addresses-for-permissions :on-press #(rf/dispatch [:open-modal :addresses-for-permissions
{:community-id id}]) {:community-id id}])
:description :text :description :text

View File

@ -13,3 +13,10 @@
:gap 4 :gap 4
:justify-content :center :justify-content :center
:margin-bottom 8}) :margin-bottom 8})
(def highest-role
{:flex-direction :row
:gap 4
:justify-content :center
:align-items :center
:margin-bottom 8})

View File

@ -4,6 +4,7 @@
[react-native.core :as rn] [react-native.core :as rn]
[status-im.common.not-implemented :as not-implemented] [status-im.common.not-implemented :as not-implemented]
[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.utils :as communities.utils]
[utils.i18n :as i18n] [utils.i18n :as i18n]
[utils.re-frame :as rf])) [utils.re-frame :as rf]))
@ -22,10 +23,14 @@
(defn view (defn view
[] []
(let [{id :community-id} (rf/sub [:get-screen-params]) (let [{id :community-id} (rf/sub [:get-screen-params])
{:keys [name color images]} (rf/sub [:communities/community id]) {:keys [name color images]} (rf/sub [:communities/community id])
accounts (rf/sub [:wallet/accounts-with-customization-color]) {:keys [highest-permission-role]} (rf/sub [:community/token-gated-overview id])
selected-addresses (rf/sub [:communities/selected-permission-addresses id])] accounts (rf/sub [:wallet/accounts-with-customization-color])
selected-addresses (rf/sub [:communities/selected-permission-addresses id])
highest-role-text
(i18n/label
(communities.utils/role->translation-key highest-permission-role))]
[rn/safe-area-view {:style style/container} [rn/safe-area-view {:style style/container}
[quo/drawer-top [quo/drawer-top
{:type :context-tag {:type :context-tag
@ -43,6 +48,19 @@
:key-fn :address :key-fn :address
:data accounts}] :data accounts}]
(when (and highest-permission-role (seq selected-addresses))
[rn/view
{:style style/highest-role}
[quo/text
{:size :paragraph-2
:style {:color colors/neutral-50}}
(i18n/label :t/eligible-to-join-as {:role ""})]
[quo/context-tag
{:type :icon
:icon :i/members
:size 24
:context highest-role-text}]])
(when (empty? selected-addresses) (when (empty? selected-addresses)
[rn/view [rn/view
{:style style/error-message} {:style style/error-message}

View File

@ -1,96 +1,56 @@
(ns status-im.contexts.communities.events (ns status-im.contexts.communities.events
(:require [clojure.set :as set] (:require
[clojure.string :as string] [clojure.string :as string]
[clojure.walk :as walk] [legacy.status-im.data-store.chats :as data-store.chats]
[legacy.status-im.data-store.chats :as data-store.chats] [legacy.status-im.data-store.communities :as data-store.communities]
[legacy.status-im.mailserver.core :as mailserver] [legacy.status-im.mailserver.core :as mailserver]
[react-native.platform :as platform] [react-native.platform :as platform]
[react-native.share :as share] [react-native.share :as share]
[schema.core :as schema] [schema.core :as schema]
[status-im.constants :as constants] [status-im.constants :as constants]
[status-im.contexts.chat.messenger.messages.link-preview.events :as link-preview.events] [status-im.contexts.chat.messenger.messages.link-preview.events :as link-preview.events]
status-im.contexts.communities.actions.community-options.events status-im.contexts.communities.actions.community-options.events
status-im.contexts.communities.actions.leave.events status-im.contexts.communities.actions.leave.events
[status-im.navigation.events :as navigation] [status-im.navigation.events :as navigation]
[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]))
(defn <-request-to-join-community-rpc (defn handle-community
[r] [{:keys [db]} [community-js]]
(set/rename-keys r (when community-js
{:communityId :community-id (let [{:keys [token-permissions
:publicKey :public-key token-permissions-check joined id]
:chatId :chat-id})) :as community} (data-store.communities/<-rpc community-js)
has-channel-perm? (fn [id-perm-tuple]
(let [{:keys [type]} (second id-perm-tuple)]
(or (= type constants/community-token-permission-can-view-channel)
(=
type
constants/community-token-permission-can-view-and-post-channel))))]
{:db (assoc-in db [:communities id] community)
:fx [[:dispatch [:communities/initialize-permission-addresses id]]
(when (not joined)
[:dispatch [:chat.ui/spectate-community id]])
(when (nil? token-permissions-check)
[:dispatch [:communities/check-permissions-to-join-community id]])
(when (some has-channel-perm? token-permissions)
[:dispatch [:communities/check-all-community-channels-permissions id]])
(when joined
[:dispatch [:communities/get-revealed-accounts id]])]})))
(defn <-requests-to-join-community-rpc (rf/reg-event-fx :communities/handle-community handle-community)
[requests key-fn]
(reduce #(assoc %1 (key-fn %2) (<-request-to-join-community-rpc %2)) {} requests))
(defn <-chats-rpc (schema/=> handle-community
[chats] [:=>
(reduce-kv (fn [acc k v] [:catn
(assoc acc [:cofx :schema.re-frame/cofx]
(name k) [:args
(-> v [:schema [:catn [:community-js map?]]]]]
(assoc :can-post? (:canPost v)) [:maybe
(dissoc :canPost) [:map
(update :members walk/stringify-keys)))) [:db [:map [:communities map?]]]
{} [:fx vector?]]]])
chats))
(defn <-categories-rpc
[categ]
(reduce-kv #(assoc %1 (name %2) %3) {} categ))
(defn <-rpc
[c]
(-> c
(set/rename-keys {:canRequestAccess :can-request-access?
:canManageUsers :can-manage-users?
:canDeleteMessageForEveryone :can-delete-message-for-everyone?
:canJoin :can-join?
:requestedToJoinAt :requested-to-join-at
:isMember :is-member?
:adminSettings :admin-settings
:tokenPermissions :token-permissions
:communityTokensMetadata :tokens-metadata
:introMessage :intro-message
:muteTill :muted-till})
(update :admin-settings
set/rename-keys
{:pinMessageAllMembersEnabled :pin-message-all-members-enabled?})
(update :members walk/stringify-keys)
(update :chats <-chats-rpc)
(update :token-permissions seq)
(update :categories <-categories-rpc)
(assoc :token-images
(reduce (fn [acc {sym :symbol image :image}]
(assoc acc sym image))
{}
(:communityTokensMetadata c)))))
(rf/reg-event-fx :communities/handle-community
(fn [{:keys [db]}
[community-js]]
(when community-js
(let [{:keys [token-permissions
token-permissions-check joined id]
:as community} (<-rpc community-js)
has-channel-perm? (fn [id-perm-tuple]
(let [{:keys [type]} (second id-perm-tuple)]
(or (= type constants/community-token-permission-can-view-channel)
(=
type
constants/community-token-permission-can-view-and-post-channel))))]
{:db (assoc-in db [:communities id] community)
:fx [[:dispatch [:communities/initialize-permission-addresses id]]
(when (not joined)
[:dispatch [:chat.ui/spectate-community id]])
(when (nil? token-permissions-check)
[:dispatch [:communities/check-permissions-to-join-community id]])
(when (some has-channel-perm? token-permissions)
[:dispatch [:communities/check-all-community-channels-permissions id]])]}))))
(rf/defn handle-removed-chats (rf/defn handle-removed-chats
[{:keys [db]} chat-ids] [{:keys [db]} chat-ids]
@ -164,7 +124,7 @@
(fn [{:keys [db]} [requests]] (fn [{:keys [db]} [requests]]
{:db (assoc db {:db (assoc db
:communities/my-pending-requests-to-join :communities/my-pending-requests-to-join
(<-requests-to-join-community-rpc requests :communityId))})) (data-store.communities/<-requests-to-join-community-rpc requests :communityId))}))
(rf/reg-event-fx :communities/get-user-requests-to-join (rf/reg-event-fx :communities/get-user-requests-to-join
(fn [_] (fn [_]
@ -240,12 +200,19 @@
(defn toggle-selected-permission-address (defn toggle-selected-permission-address
[{:keys [db]} [address community-id]] [{:keys [db]} [address community-id]]
{:db (update-in db (let [selected-permission-addresses
[:communities community-id :selected-permission-addresses] (get-in db [:communities community-id :selected-permission-addresses])
(fn [selected-addresses] updated-selected-permission-addresses
(if (contains? selected-addresses address) (if (contains? selected-permission-addresses address)
(disj selected-addresses address) (disj selected-permission-addresses address)
(conj selected-addresses address))))}) (conj selected-permission-addresses address))]
{:db (assoc-in db
[:communities community-id :selected-permission-addresses]
updated-selected-permission-addresses)
:fx [(when community-id
[:dispatch
[:communities/check-permissions-to-join-community community-id
updated-selected-permission-addresses :based-on-client-selection]])]}))
(rf/reg-event-fx :communities/toggle-selected-permission-address (rf/reg-event-fx :communities/toggle-selected-permission-address
toggle-selected-permission-address) toggle-selected-permission-address)
@ -255,7 +222,8 @@
(when community-id (when community-id
{:db (assoc-in db {:db (assoc-in db
[:communities community-id :selected-permission-addresses] [:communities community-id :selected-permission-addresses]
(get-in db [:communities community-id :previous-permission-addresses]))}))) (get-in db [:communities community-id :previous-permission-addresses]))
:fx [[:dispatch [:communities/check-permissions-to-join-community community-id]]]})))
(rf/reg-event-fx :communities/share-community-channel-url-with-data (rf/reg-event-fx :communities/share-community-channel-url-with-data
(fn [_ [chat-id]] (fn [_ [chat-id]]
@ -405,3 +373,54 @@
(if pop-to-root? (if pop-to-root?
[:dispatch [:chat/pop-to-root-and-navigate-to-chat chat-id]] [:dispatch [:chat/pop-to-root-and-navigate-to-chat chat-id]]
[:dispatch [:chat/navigate-to-chat chat-id]])]}))) [:dispatch [:chat/navigate-to-chat chat-id]])]})))
(defn get-revealed-accounts
[{:keys [db]} [community-id]]
(let [{:keys [joined fetching-revealed-accounts]
:as community} (get-in db [:communities community-id])]
(when (and community joined (not fetching-revealed-accounts))
{:db (assoc-in db [:communities community-id :fetching-revealed-accounts] true)
:json-rpc/call
[{:method "wakuext_getRevealedAccounts"
:params [community-id (get-in db [:profile/profile :public-key])]
:js-response true
:on-success [:communities/get-revealed-accounts-success community-id]
:on-error (fn [err]
(log/error {:message "failed to fetch revealed accounts"
:community-id community-id
:err err})
(rf/dispatch [:communities/get-revealed-accounts-failed community-id]))}]})))
(rf/reg-event-fx :communities/get-revealed-accounts get-revealed-accounts)
(schema/=> get-revealed-accounts
[:=>
[:catn
[:cofx :schema.re-frame/cofx]
[:args
[:schema [:catn [:community-id [:? :string]]]]]]
[:maybe
[:map
[:db map?]
[:json-rpc/call :schema.common/rpc-call]]]])
(rf/reg-event-fx :communities/get-revealed-accounts-success
(fn [{:keys [db]} [community-id revealed-accounts-js]]
(when-let [community (get-in db [:communities community-id])]
(let [revealed-accounts
(reduce
(fn [acc {:keys [address] :as revealed-account}]
(assoc acc address (dissoc revealed-account :address)))
{}
(data-store.communities/<-revealed-accounts-rpc revealed-accounts-js))
community-with-revealed-accounts
(-> community
(assoc :revealed-accounts revealed-accounts)
(dissoc :fetching-revealed-accounts))]
{:db (assoc-in db [:communities community-id] community-with-revealed-accounts)}))))
(rf/reg-event-fx :communities/get-revealed-accounts-failed
(fn [{:keys [db]} [community-id]]
(when (get-in db [:communities community-id])
{:db (update-in db [:communities community-id] dissoc :fetching-revealed-accounts)})))

View File

@ -2,6 +2,7 @@
(:require [cljs.test :refer [deftest is testing]] (:require [cljs.test :refer [deftest is testing]]
[legacy.status-im.mailserver.core :as mailserver] [legacy.status-im.mailserver.core :as mailserver]
matcher-combinators.test matcher-combinators.test
[status-im.constants :as constants]
[status-im.contexts.chat.messenger.messages.link-preview.events :as link-preview.events] [status-im.contexts.chat.messenger.messages.link-preview.events :as link-preview.events]
[status-im.contexts.communities.events :as events])) [status-im.contexts.communities.events :as events]))
@ -193,3 +194,79 @@
(is (match? (is (match?
nil nil
(events/spectate-community-success {} [])))))) (events/spectate-community-success {} []))))))
(deftest get-revealed-accounts
(let [community {:id community-id}]
(testing "given a unjoined community"
(is (match?
nil
(events/get-revealed-accounts {:db {:communities {community-id community}}} [community-id]))))
(testing "given a already :fetching-revealed-accounts community"
(is (match?
nil
(events/get-revealed-accounts
{:db {:communities {community-id (assoc community :fetching-revealed-accounts true)}}}
[community-id]))))
(testing "given joined community"
(let [community (assoc community :joined true)
db {:communities {community-id community}
:profile/profile {:public-key "profile-public-key"}}
effects (events/get-revealed-accounts {:db db} [community-id])]
(is (match? (assoc-in db [:communities community-id :fetching-revealed-accounts] true)
(:db effects)))
(is (match? {:method "wakuext_getRevealedAccounts"
:params [community-id "profile-public-key"]}
(-> effects :json-rpc/call first (select-keys [:method :params]))))))))
(deftest handle-community
(let [community {:id community-id}]
(testing "given a unjoined community"
(let [effects (events/handle-community {} [community])]
(is (match? community-id
(-> effects :db :communities (get community-id) :id)))
(is (match?
[[:dispatch [:communities/initialize-permission-addresses community-id]]
[:dispatch [:chat.ui/spectate-community community-id]]
[:dispatch [:communities/check-permissions-to-join-community community-id]]]
(filter some? (:fx effects))))))
(testing "given a joined community"
(let [community (assoc community :joined true)
effects (events/handle-community {} [community])]
(is (match?
[[:dispatch [:communities/initialize-permission-addresses community-id]]
[:dispatch [:communities/check-permissions-to-join-community community-id]]
[:dispatch [:communities/get-revealed-accounts 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 [:communities/initialize-permission-addresses community-id]]
[:dispatch [:chat.ui/spectate-community community-id]]]
(filter some? (:fx effects))))))
(testing "given a community with view channel permission"
(let [community (assoc community
:token-permissions
[["perm-id" {:type constants/community-token-permission-can-view-channel}]])
effects (events/handle-community {} [community])]
(is (match?
[[:dispatch [:communities/initialize-permission-addresses community-id]]
[:dispatch [:chat.ui/spectate-community community-id]]
[:dispatch [:communities/check-permissions-to-join-community community-id]]
[:dispatch
[:communities/check-all-community-channels-permissions community-id]]]
(filter some? (:fx effects))))))
(testing "given a community with post in channel permission"
(let [community (assoc community
:token-permissions
[["perm-id"
{:type constants/community-token-permission-can-view-and-post-channel}]])
effects (events/handle-community {} [community])]
(is (match?
[[:dispatch [:communities/initialize-permission-addresses community-id]]
[:dispatch [:chat.ui/spectate-community community-id]]
[:dispatch [:communities/check-permissions-to-join-community community-id]]
[:dispatch
[:communities/check-all-community-channels-permissions community-id]]]
(filter some? (:fx effects))))))))

View File

@ -34,26 +34,31 @@
:communities/check-all-community-channels-permissions}))}]]]}))) :communities/check-all-community-channels-permissions}))}]]]})))
(rf/reg-event-fx :communities/check-permissions-to-join-community-success (rf/reg-event-fx :communities/check-permissions-to-join-community-success
(fn [{:keys [db]} [community-id result]] (fn [{:keys [db]} [community-id based-on-client-selection? result]]
{:db (-> db (let [token-permissions-check (cond-> result
(assoc-in [:communities community-id :checking-permissions?] false) based-on-client-selection? (assoc :based-on-client-selection? true))]
(assoc-in [:communities community-id :token-permissions-check] result))})) {:db (-> db
(assoc-in [:communities community-id :checking-permissions?] false)
(assoc-in [:communities community-id :token-permissions-check]
token-permissions-check))})))
(rf/reg-event-fx :communities/check-permissions-to-join-community-failed (rf/reg-event-fx :communities/check-permissions-to-join-community-failed
(fn [{:keys [db]} [community-id]] (fn [{:keys [db]} [community-id]]
{:db (assoc-in db [:communities community-id :checking-permissions?] false)})) {:db (assoc-in db [:communities community-id :checking-permissions?] false)}))
(rf/reg-event-fx :communities/check-permissions-to-join-community (rf/reg-event-fx :communities/check-permissions-to-join-community
(fn [{:keys [db]} [community-id]] (fn [{:keys [db]} [community-id addresses based-on-client-selection?]]
(when-let [community (get-in db [:communities community-id])] (when-let [community (get-in db [:communities community-id])]
(when-not (:checking-permissions? community) (when-not (:checking-permissions? community)
{:db (-> db {:db (-> db
(assoc-in [:communities community-id :checking-permissions?] true) (assoc-in [:communities community-id :checking-permissions?] true)
(assoc-in [:communities community-id :can-request-access?] false)) (assoc-in [:communities community-id :can-request-access?] false))
:json-rpc/call [{:method "wakuext_checkPermissionsToJoinCommunity" :json-rpc/call [{:method "wakuext_checkPermissionsToJoinCommunity"
:params [{:communityId community-id}] :params [(cond-> {:communityId community-id}
addresses
(assoc :addresses addresses))]
:on-success [:communities/check-permissions-to-join-community-success :on-success [:communities/check-permissions-to-join-community-success
community-id] community-id based-on-client-selection?]
:on-error (fn [err] :on-error (fn [err]
(rf/dispatch (rf/dispatch
[:communities/check-permissions-to-join-community-failed [:communities/check-permissions-to-join-community-failed

View File

@ -16,6 +16,7 @@
[status-im.contexts.communities.actions.community-options.view :as options] [status-im.contexts.communities.actions.community-options.view :as options]
[status-im.contexts.communities.overview.style :as style] [status-im.contexts.communities.overview.style :as style]
[status-im.contexts.communities.overview.utils :as utils] [status-im.contexts.communities.overview.utils :as utils]
[status-im.contexts.communities.utils :as communities.utils]
[utils.debounce :as debounce] [utils.debounce :as debounce]
[utils.i18n :as i18n] [utils.i18n :as i18n]
[utils.re-frame :as rf])) [utils.re-frame :as rf]))
@ -140,7 +141,11 @@
[] []
(fn [{:keys [id color]}] (fn [{:keys [id color]}]
(let [{:keys [can-request-access? (let [{:keys [can-request-access?
number-of-hold-tokens tokens]} (rf/sub [:community/token-gated-overview id])] number-of-hold-tokens tokens
highest-permission-role]} (rf/sub [:community/token-gated-overview id])
highest-role-text
(i18n/label
(communities.utils/role->translation-key highest-permission-role :t/member))]
[rn/view {:style (style/token-gated-container)} [rn/view {:style (style/token-gated-container)}
[rn/view [rn/view
{:style {:padding-horizontal 12 {:style {:padding-horizontal 12
@ -150,7 +155,7 @@
:flex 1}} :flex 1}}
[quo/text {:weight :medium} [quo/text {:weight :medium}
(if can-request-access? (if can-request-access?
(i18n/label :t/you-eligible-to-join) (i18n/label :t/you-eligible-to-join-as {:role highest-role-text})
(i18n/label :t/you-not-eligible-to-join))] (i18n/label :t/you-not-eligible-to-join))]
[info-button]] [info-button]]
(when (pos? number-of-hold-tokens) (when (pos? number-of-hold-tokens)
@ -163,23 +168,26 @@
{:tokens tokens {:tokens tokens
:padding? true}] :padding? true}]
[quo/button [quo/button
{:on-press #(join-gated-community id) {:on-press
(if config/community-accounts-selection-enabled?
#(rf/dispatch [:open-modal :community-account-selection {:community-id id}])
#(join-gated-community id))
:accessibility-label :join-community-button :accessibility-label :join-community-button
:customization-color color :customization-color color
:container-style {:margin-horizontal 12 :margin-top 12 :margin-bottom 12} :container-style {:margin-horizontal 12 :margin-top 12 :margin-bottom 12}
:disabled? (not can-request-access?) :disabled? (not can-request-access?)
:icon-left (if can-request-access? :i/unlocked :i/locked)} :icon-left (if can-request-access? :i/unlocked :i/locked)}
(i18n/label :t/join-open-community)]]))) (i18n/label :t/join-open-community)]])))
(defn join-community (defn join-community
[{:keys [joined color permissions token-permissions id] :as community} [{:keys [joined color permissions token-permissions membership-permissions? id] :as community}
pending?] pending?]
(let [access-type (get-access-type (:access permissions)) (let [access-type (get-access-type (:access permissions))
unknown-access? (= access-type :unknown-access) unknown-access? (= access-type :unknown-access)
invite-only? (= access-type :invite-only)] invite-only? (= access-type :invite-only)]
[:<> [:<>
(when-not (or joined pending? invite-only? unknown-access?) (when-not (or joined pending? invite-only? unknown-access?)
(if (seq token-permissions) (if membership-permissions?
[token-gates community] [token-gates community]
[quo/button [quo/button
{:on-press {:on-press
@ -190,7 +198,7 @@
:accessibility-label :show-request-to-join-screen-button :accessibility-label :show-request-to-join-screen-button
:customization-color color :customization-color color
:icon-left :i/communities} :icon-left :i/communities}
(i18n/label :t/request-to-join-community)])) (i18n/label :t/request-to-join)]))
(when (not (or joined pending? token-permissions)) (when (not (or joined pending? token-permissions))
[quo/text [quo/text

View File

@ -0,0 +1,13 @@
(ns status-im.contexts.communities.utils
(:require
[status-im.constants :as constants]))
(defn role->translation-key
([role] (role->translation-key role nil))
([role fallback-to]
(condp = role
constants/community-token-permission-become-token-owner :t/token-owner
constants/community-token-permission-become-token-master :t/token-master
constants/community-token-permission-become-admin :t/admin
constants/community-token-permission-become-member :t/member
fallback-to)))

View File

@ -297,32 +297,62 @@
(fn [collapsed-categories [_ community-id]] (fn [collapsed-categories [_ community-id]]
(get collapsed-categories community-id))) (get collapsed-categories community-id)))
(defn- permission-id->permission-value
[token-permissions permission-id]
(get (into {} token-permissions) permission-id))
(re-frame/reg-sub (re-frame/reg-sub
:community/token-gated-overview :community/token-gated-overview
(fn [[_ community-id]] (fn [[_ community-id]]
[(re-frame/subscribe [:communities/community community-id])]) [(re-frame/subscribe [:communities/community community-id])])
(fn [[{:keys [token-permissions-check token-permissions checking-permissions? token-images]}] _] (fn [[{:keys [token-permissions-check token-permissions checking-permissions? token-images]}] _]
{:can-request-access? (:satisfied token-permissions-check) (let [can-request-access? (:satisfied token-permissions-check)
:number-of-hold-tokens (reduce role-permissions #{constants/community-token-permission-become-admin
(fn [acc [_ {:keys [criteria]}]] constants/community-token-permission-become-member
(reduce #(+ %1 (if %2 1 0)) acc criteria)) constants/community-token-permission-become-token-master
0 constants/community-token-permission-become-token-owner}
(:permissions token-permissions-check)) highest-permission-role
:tokens (->> token-permissions (when can-request-access?
(filter (fn [[_ {:keys [type]}]] (->> token-permissions-check
(= type constants/community-token-permission-become-member))) :permissions
(map (fn [[perm-key {:keys [token_criteria]}]] (reduce-kv
(let [check-criteria (get-in token-permissions-check (fn [highest-permission-role permission-id {:keys [criteria]}]
[:permissions perm-key :criteria])] (if-let [permission-type
(map (and (first criteria)
(fn [{sym :symbol amount :amount} sufficient?] (some #{(:type (permission-id->permission-value token-permissions
{:symbol sym permission-id))}
:sufficient? (when (seq check-criteria) sufficient?) role-permissions))]
:loading? checking-permissions? (if highest-permission-role
:amount amount (min highest-permission-role permission-type)
:img-src (get token-images sym)}) permission-type)
token_criteria highest-permission-role))
(or check-criteria token_criteria))))))})) nil)))
highest-permission-role (if (and can-request-access? (nil? highest-permission-role))
constants/community-token-permission-become-member
highest-permission-role)]
{:can-request-access? can-request-access?
:highest-permission-role highest-permission-role
:number-of-hold-tokens (reduce
(fn [acc [_ {:keys [criteria]}]]
(reduce #(+ %1 (if %2 1 0)) acc criteria))
0
(:permissions token-permissions-check))
:tokens (->>
token-permissions
(filter (fn [[_ {:keys [type]}]]
(= type constants/community-token-permission-become-member)))
(map (fn [[perm-key {:keys [token_criteria]}]]
(let [check-criteria (get-in token-permissions-check
[:permissions perm-key :criteria])]
(map
(fn [{sym :symbol amount :amount} sufficient?]
{:symbol sym
:sufficient? (when (seq check-criteria) sufficient?)
:loading? checking-permissions?
:amount amount
:img-src (get token-images sym)})
token_criteria
(or check-criteria token_criteria))))))})))
(re-frame/reg-sub (re-frame/reg-sub
:community/images :community/images

View File

@ -1,6 +1,7 @@
(ns status-im.subs.communities-test (ns status-im.subs.communities-test
(:require (:require
[cljs.test :refer [is testing]] [cljs.test :refer [is testing]]
matcher-combinators.test
[re-frame.db :as rf-db] [re-frame.db :as rf-db]
[status-im.constants :as constants] [status-im.constants :as constants]
status-im.subs.communities status-im.subs.communities
@ -17,7 +18,7 @@
(swap! rf-db/app-db assoc (swap! rf-db/app-db assoc
:communities :communities
raw-communities) raw-communities)
(is (= raw-communities (rf/sub [sub-name])))))) (is (match? raw-communities (rf/sub [sub-name]))))))
(h/deftest-sub :communities/section-list (h/deftest-sub :communities/section-list
[sub-name] [sub-name]
@ -26,29 +27,29 @@
:communities :communities
{"0x1" {:name "civilized monkeys"} {"0x1" {:name "civilized monkeys"}
"0x2" {:name "Civilized rats"}}) "0x2" {:name "Civilized rats"}})
(is (= [{:title "C" (is (match? [{:title "C"
:data [{:name "civilized monkeys"} :data [{:name "civilized monkeys"}
{:name "Civilized rats"}]}] {:name "Civilized rats"}]}]
(rf/sub [sub-name])))) (rf/sub [sub-name]))))
(testing "sorts by section ascending" (testing "sorts by section ascending"
(swap! rf-db/app-db assoc (swap! rf-db/app-db assoc
:communities :communities
{"0x3" {:name "Memorable"} {"0x3" {:name "Memorable"}
"0x1" {:name "Civilized monkeys"}}) "0x1" {:name "Civilized monkeys"}})
(is (= [{:title "C" :data [{:name "Civilized monkeys"}]} (is (match? [{:title "C" :data [{:name "Civilized monkeys"}]}
{:title "M" :data [{:name "Memorable"}]}] {:title "M" :data [{:name "Memorable"}]}]
(rf/sub [sub-name])))) (rf/sub [sub-name]))))
(testing "builds default section for communities without a name" (testing "builds default section for communities without a name"
(swap! rf-db/app-db assoc (swap! rf-db/app-db assoc
:communities :communities
{"0x2" {:id "0x2"} {"0x2" {:id "0x2"}
"0x1" {:id "0x1"}}) "0x1" {:id "0x1"}})
(is (= [{:title "" (is (match? [{:title ""
:data [{:id "0x2"} :data [{:id "0x2"}
{:id "0x1"}]}] {:id "0x1"}]}]
(rf/sub [sub-name]))))) (rf/sub [sub-name])))))
(h/deftest-sub :communities/unviewed-counts (h/deftest-sub :communities/unviewed-counts
[sub-name] [sub-name]
@ -64,17 +65,17 @@
"0x102" {:community-id community-id "0x102" {:community-id community-id
:unviewed-mentions-count 5 :unviewed-mentions-count 5
:unviewed-messages-count 1}}) :unviewed-messages-count 1}})
(is (= {:unviewed-messages-count 3 (is (match? {:unviewed-messages-count 3
:unviewed-mentions-count 8} :unviewed-mentions-count 8}
(rf/sub [sub-name community-id])))) (rf/sub [sub-name community-id]))))
(testing "defaults to zero when count keys are not present" (testing "defaults to zero when count keys are not present"
(swap! rf-db/app-db assoc (swap! rf-db/app-db assoc
:chats :chats
{"0x100" {:community-id community-id}}) {"0x100" {:community-id community-id}})
(is (= {:unviewed-messages-count 0 (is (match? {:unviewed-messages-count 0
:unviewed-mentions-count 0} :unviewed-mentions-count 0}
(rf/sub [sub-name community-id]))))) (rf/sub [sub-name community-id])))))
(h/deftest-sub :communities/categorized-channels (h/deftest-sub :communities/categorized-channels
[sub-name] [sub-name]
@ -350,16 +351,16 @@
(swap! rf-db/app-db assoc (swap! rf-db/app-db assoc
:communities/my-pending-requests-to-join :communities/my-pending-requests-to-join
{}) {})
(is (= {} (is (match? {}
(rf/sub [sub-name])))) (rf/sub [sub-name]))))
(testing "users requests to join different communities" (testing "users requests to join different communities"
(swap! rf-db/app-db assoc (swap! rf-db/app-db assoc
:communities/my-pending-requests-to-join :communities/my-pending-requests-to-join
{:community-id-1 {:id :request-id-1} {:community-id-1 {:id :request-id-1}
:community-id-2 {:id :request-id-2}}) :community-id-2 {:id :request-id-2}})
(is (= {:community-id-1 {:id :request-id-1} (is (match? {:community-id-1 {:id :request-id-1}
:community-id-2 {:id :request-id-2}} :community-id-2 {:id :request-id-2}}
(rf/sub [sub-name]))))) (rf/sub [sub-name])))))
(h/deftest-sub :communities/my-pending-request-to-join (h/deftest-sub :communities/my-pending-request-to-join
[sub-name] [sub-name]
@ -367,15 +368,15 @@
(swap! rf-db/app-db assoc (swap! rf-db/app-db assoc
:communities/my-pending-requests-to-join :communities/my-pending-requests-to-join
{}) {})
(is (= nil (is (match? nil
(rf/sub [sub-name :community-id-1])))) (rf/sub [sub-name :community-id-1]))))
(testing "users request to join a specific communities" (testing "users request to join a specific communities"
(swap! rf-db/app-db assoc (swap! rf-db/app-db assoc
:communities/my-pending-requests-to-join :communities/my-pending-requests-to-join
{:community-id-1 {:id :request-id-1} {:community-id-1 {:id :request-id-1}
:community-id-2 {:id :request-id-2}}) :community-id-2 {:id :request-id-2}})
(is (= :request-id-1 (is (match? :request-id-1
(rf/sub [sub-name :community-id-1]))))) (rf/sub [sub-name :community-id-1])))))
(h/deftest-sub :community/token-gated-overview (h/deftest-sub :community/token-gated-overview
[sub-name] [sub-name]
@ -385,6 +386,7 @@
community {:id community-id community {:id community-id
:checking-permissions? checking-permissions? :checking-permissions? checking-permissions?
:permissions {:access 3} :permissions {:access 3}
:highest-permission-role constants/community-token-permission-become-admin
:token-images {"ETH" token-image-eth} :token-images {"ETH" token-image-eth}
:token-permissions [[:permission-id-01 :token-permissions [[:permission-id-01
{:id "permission-id-01" {:id "permission-id-01"
@ -451,14 +453,14 @@
:outroMessage "bla" :outroMessage "bla"
:verified false}] :verified false}]
(swap! rf-db/app-db assoc-in [:communities community-id] community) (swap! rf-db/app-db assoc-in [:communities community-id] community)
(is (= {:can-request-access? true (is (match? {:can-request-access? true
:number-of-hold-tokens 2 :number-of-hold-tokens 2
:tokens [[{:symbol "ETH" :tokens [[{:symbol "ETH"
:amount "0.001" :amount "0.001"
:sufficient? nil :sufficient? nil
:loading? checking-permissions? :loading? checking-permissions?
:img-src token-image-eth}]]} :img-src token-image-eth}]]}
(rf/sub [sub-name community-id]))))) (rf/sub [sub-name community-id])))))
(h/deftest-sub :communities/airdrop-account (h/deftest-sub :communities/airdrop-account
[sub-name] [sub-name]

View File

@ -184,7 +184,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-member": "Join as a Member", "join-as-a": "Join as a {{role}}",
"all-addresses": "All addresses", "all-addresses": "All addresses",
"for-airdrops": "For airdrops", "for-airdrops": "For airdrops",
"members-label": "Members", "members-label": "Members",
@ -2016,6 +2016,9 @@
"mentions": "Mentions", "mentions": "Mentions",
"mention": "Mention", "mention": "Mention",
"admin": "Admin", "admin": "Admin",
"member": "Member",
"token-master": "Token Master",
"token-owner": "Token Owner",
"replies": "Replies", "replies": "Replies",
"replied": "Replied", "replied": "Replied",
"identity-verification": "Identity verification", "identity-verification": "Identity verification",
@ -2252,7 +2255,9 @@
"sync-devices-complete-title": "Device sync complete!", "sync-devices-complete-title": "Device sync complete!",
"sync-devices-complete-sub-title": "Your devices are now in sync", "sync-devices-complete-sub-title": "Your devices are now in sync",
"synced-with": "Synced with", "synced-with": "Synced with",
"eligible-to-join-as": "Eligible to join as {{role}}",
"you-eligible-to-join": "Youre eligible to join", "you-eligible-to-join": "Youre eligible to join",
"you-eligible-to-join-as": "Youre eligible to join as {{role}}",
"you-not-eligible-to-join": "Youre not eligible to join", "you-not-eligible-to-join": "Youre not eligible to join",
"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:",
"token-gated-communities": "Token gated communities", "token-gated-communities": "Token gated communities",