Account selection: Implement features related to editing shared addresses (#19177)

* Fix schema error

* Implement feature to edit shared addresses

* fix: show tokens when sharing all future addresses toggled

* fix: eligibility not checked when toggling all addresses

* Revert "fix: show tokens when sharing all future addresses toggled"

This reverts commit 8443a3432e.

* fix: don't fetch balances on mount if toggled share all

---------

Co-authored-by: Cristian Lungu <lungucristian95@gmail.com>
This commit is contained in:
Icaro Motta 2024-03-18 12:07:15 -03:00 committed by GitHub
parent 6c5242b3e0
commit 7948582e46
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 1403 additions and 566 deletions

View File

@ -59,4 +59,15 @@
:button-one-label "Request to join"}]) :button-one-label "Request to join"}])
(h/is-truthy (h/get-by-text "Eligible to join as")) (h/is-truthy (h/get-by-text "Eligible to join as"))
(h/is-truthy (h/get-by-text "Admin")) (h/is-truthy (h/get-by-text "Admin"))
(h/is-truthy (h/get-by-text "Request to join")))
(h/test "render with top description with custom text & 1 action"
(render [bottom-actions/view
{:description :top
:role :admin
:actions :one-action
:description-top-text "You'll be an"
:button-one-label "Request to join"}])
(h/is-truthy (h/get-by-text "You'll be an"))
(h/is-truthy (h/get-by-text "Admin"))
(h/is-truthy (h/get-by-text "Request to join")))) (h/is-truthy (h/get-by-text "Request to join"))))

View File

@ -19,6 +19,7 @@
[:actions [:maybe [:enum :one-action :two-actions]]] [:actions [:maybe [:enum :one-action :two-actions]]]
[:description {:optional true} [:maybe [:enum :top :bottom :top-error]]] [:description {:optional true} [:maybe [:enum :top :bottom :top-error]]]
[:description-text {:optional true} [:maybe :string]] [:description-text {:optional true} [:maybe :string]]
[:description-top-text {:optional true} [:maybe :string]]
[:error-message {:optional true} [:maybe :string]] [:error-message {:optional true} [:maybe :string]]
[:role {:optional true} [:maybe [:enum :admin :member :token-master :owner]]] [:role {:optional true} [:maybe [:enum :admin :member :token-master :owner]]]
[:button-one-label {:optional true} [:maybe :string]] [:button-one-label {:optional true} [:maybe :string]]
@ -38,8 +39,8 @@
:owner :i/crown}) :owner :i/crown})
(defn- view-internal (defn- view-internal
[{:keys [actions description description-text error-message role button-one-label button-two-label [{:keys [actions description description-text description-top-text error-message role button-one-label
blur? button-one-props button-two-props theme scroll? container-style]}] button-two-label blur? button-one-props button-two-props theme scroll? container-style]}]
[rn/view [rn/view
{:style (merge (style/container scroll? blur? theme) container-style)} {:style (merge (style/container scroll? blur? theme) container-style)}
(when (= description :top-error) (when (= description :top-error)
@ -54,12 +55,11 @@
error-message]]) error-message]])
(when (and (= description :top) role) (when (and (= description :top) role)
[rn/view [rn/view {:style style/description-top}
{:style style/description-top}
[text/text [text/text
{:size :paragraph-2 {:size :paragraph-2
:style (style/description-top-text scroll? blur? theme)} :style (style/description-top-text scroll? blur? theme)}
(i18n/label :t/eligible-to-join-as)] (or description-top-text (i18n/label :t/eligible-to-join-as))]
[context-tag/view [context-tag/view
{:type :icon {:type :icon
:size 24 :size 24

View File

@ -5,6 +5,14 @@
(def ^:private ?cofx (def ^:private ?cofx
[:map]) [:map])
(def ^:private ?event
[:and
[:vector {:min 1} :any]
[:catn
[:event-id keyword?]
[:event-args [:* :any]]]])
(defn register-schemas (defn register-schemas
[] []
(registry/register ::cofx ?cofx)) (registry/register ::cofx ?cofx)
(registry/register ::event ?event))

View File

@ -0,0 +1,102 @@
(ns status-im.contexts.communities.actions.accounts-selection.effects
(:require
[promesa.core :as p]
[schema.core :as schema]
[status-im.common.json-rpc.events :as rpc]
[utils.re-frame :as rf]))
(defn- generate-requests-for-signing
[pub-key community-id addresses-to-reveal]
(p/create
(fn [p-resolve p-reject]
(rpc/call
{:method :wakuext_generateJoiningCommunityRequestsForSigning
:params [pub-key community-id addresses-to-reveal]
:on-success p-resolve
:on-error #(p-reject (str "failed to generate requests for signing\n" %))}))))
(defn- sign-data
[sign-params password]
(p/create
(fn [p-resolve p-reject]
(rpc/call
{:method :wakuext_signData
:params [(map #(assoc % :password password) sign-params)]
:on-success p-resolve
:on-error #(p-reject (str "failed to sign data\n" %))}))))
(defn- edit-shared-addresses-for-community
[community-id signatures addresses-to-reveal airdrop-address]
(p/create
(fn [p-resolve p-reject]
(rpc/call
{:method :wakuext_editSharedAddressesForCommunity
:params [{:communityId community-id
:signatures signatures
:addressesToReveal addresses-to-reveal
:airdropAddress airdrop-address}]
:js-response true
:on-success p-resolve
:on-error p-reject}))))
(defn- request-to-join
[community-id signatures addresses-to-reveal airdrop-address]
(p/create
(fn [p-resolve p-reject]
(rpc/call
{:method :wakuext_requestToJoinCommunity
:params [{:communityId community-id
:signatures signatures
:addressesToReveal addresses-to-reveal
:airdropAddress airdrop-address}]
:js-response true
:on-success p-resolve
:on-error p-reject}))))
(defn- run-callback-or-event
[callback-or-event result]
(cond (fn? callback-or-event)
(callback-or-event result)
(vector? callback-or-event)
(rf/dispatch (conj callback-or-event result))))
(defn- sign-and-call-endpoint
[{:keys [community-id password pub-key
addresses-to-reveal airdrop-address
on-success on-error
callback]}]
(-> (p/let [sign-params (generate-requests-for-signing pub-key community-id addresses-to-reveal)
signatures (sign-data sign-params password)
result (callback community-id
signatures
addresses-to-reveal
airdrop-address)]
(run-callback-or-event on-success result))
(p/catch #(run-callback-or-event on-error %))))
(schema/=> sign-and-call-endpoint
[:=>
[:cat
[:map {:closed true}
[:community-id string?]
[:password string?]
[:pub-key string?]
[:addresses-to-reveal
[:or [:set string?]
[:sequential string?]]]
[:airdrop-address string?]
[:on-success [:or fn? :schema.re-frame/event]]
[:on-error [:or fn? :schema.re-frame/event]]
[:callback fn?]]]
:any])
(rf/reg-fx :effects.community/edit-shared-addresses
(fn [opts]
(sign-and-call-endpoint
(assoc opts :callback edit-shared-addresses-for-community))))
(rf/reg-fx :effects.community/request-to-join
(fn [opts]
(sign-and-call-endpoint
(assoc opts :callback request-to-join))))

View File

@ -0,0 +1,103 @@
(ns status-im.contexts.communities.actions.accounts-selection.events
(:require
status-im.contexts.communities.actions.accounts-selection.effects
[status-im.contexts.communities.utils :as utils]
[taoensso.timbre :as log]
[utils.re-frame :as rf]))
(defn initialize-permission-addresses
[{:keys [db]} [community-id]]
(let [{:keys [joined]} (get-in db [:communities community-id])
pending-requests (get-in db [:communities/my-pending-requests-to-join community-id])
init-using-revealed-accounts? (or joined (seq pending-requests))]
{:fx [(if init-using-revealed-accounts?
[:dispatch
[:communities/get-revealed-accounts community-id
[:communities/do-init-permission-addresses community-id]]]
[:dispatch [:communities/do-init-permission-addresses community-id]])]}))
(rf/reg-event-fx :communities/initialize-permission-addresses initialize-permission-addresses)
(defn do-init-permission-addresses
[{:keys [db]} [community-id revealed-accounts]]
(let [wallet-accounts (utils/sorted-non-watch-only-accounts db)
addresses-to-reveal (if (seq revealed-accounts)
(set (keys revealed-accounts))
;; Reveal all addresses as fallback.
(set (map :address wallet-accounts)))
;; When there are no revealed addresses, such as when joining a
;; community, use first address for airdrops.
airdrop-address (or (->> revealed-accounts
vals
(filter :airdrop-address?)
first
:address)
(->> wallet-accounts
first
:address))]
{:db (-> db
;; Set to false by default while we don't persist the user's choice
;; in status-go, otherwise whenever the view is mounted, the choice
;; of selected addresses won't be respected.
(assoc-in [:communities/selected-share-all-addresses community-id] false)
(assoc-in [:communities/all-addresses-to-reveal community-id] addresses-to-reveal)
(assoc-in [:communities/all-airdrop-addresses community-id] airdrop-address))
:fx [[:dispatch
[:communities/check-permissions-to-join-community
community-id addresses-to-reveal :based-on-client-selection]]
;; Pre-fetch permissions check so that when first opening the
;; Addresses for Permissions screen the highest permission role is
;; already available and no incorrect information flashes on screen.
[:dispatch
[:communities/check-permissions-to-join-during-selection community-id
addresses-to-reveal]]]}))
(rf/reg-event-fx :communities/do-init-permission-addresses do-init-permission-addresses)
(defn edit-shared-addresses
"This event will effectively persist the choice of addresses and airdrop address
in status-go. It can either take addresses to share AND an airdrop address, or
just addresses to share OR an airdrop address. This is because when the user
is selecting an airdrop address, we must submit to status-go the current
choice of addresses to share, and vice-versa. If we omit addresses to share,
status-go will default to all available."
[{:keys [db]} [{:keys [community-id password on-success addresses airdrop-address]}]]
(let [pub-key (get-in db [:profile/profile :public-key])
wallet-accounts (utils/sorted-non-watch-only-accounts db)
addresses-to-reveal (if (seq addresses)
(set addresses)
(get-in db [:communities/all-addresses-to-reveal community-id]))
new-airdrop-address (if (contains? addresses-to-reveal airdrop-address)
airdrop-address
(->> wallet-accounts
(filter #(contains? addresses-to-reveal (:address %)))
first
:address))]
{:fx [[:effects.community/edit-shared-addresses
{:community-id community-id
:password password
:pub-key pub-key
:addresses-to-reveal addresses-to-reveal
:airdrop-address new-airdrop-address
:on-success (fn []
(when (fn? on-success)
(on-success addresses-to-reveal new-airdrop-address))
(rf/dispatch [:communities/edit-shared-addresses-success
community-id addresses-to-reveal airdrop-address]))
:on-error [:communities/edit-shared-addresses-failure community-id]}]]}))
(rf/reg-event-fx :communities/edit-shared-addresses edit-shared-addresses)
(rf/reg-event-fx :communities/edit-shared-addresses-success
(fn [_ [community-id addresses-to-reveal airdrop-address]]
{:fx [[:dispatch [:communities/set-airdrop-address community-id airdrop-address]]
[:dispatch [:communities/set-addresses-to-reveal community-id addresses-to-reveal]]]}))
(rf/reg-event-fx :communities/edit-shared-addresses-failure
(fn [{:keys [db]} [community-id error]]
(log/error "failed to edit shared addresses"
{:event :communities/edit-shared-addresses
:community-id community-id
:error error})
{:db (assoc-in db [:password-authentication :error] error)}))

View File

@ -0,0 +1,169 @@
(ns status-im.contexts.communities.actions.accounts-selection.events-test
(:require [cljs.test :refer [deftest is testing]]
matcher-combinators.test
[status-im.contexts.communities.actions.accounts-selection.events :as sut]))
(def community-id "0x99")
(def password "password")
(def wallet-accounts
{"0xA" {:address "0xA"
:watch-only? true
:position 2
:color :red
:emoji "🦇"}
"0xB" {:address "0xB"
:position 0
:color :blue
:emoji "🐈"}
"0xC" {:address "0xC"
:position 1
:color :orange
:emoji "🛏️"}})
(def permissioned-accounts
[{:address "0xB"
:position 0
:color :blue
:emoji "🐈"
:airdrop-address? true
:reveal? true}
{:address "0xC"
:position 1
:color :orange
:emoji "🛏️"
:airdrop-address? false
:reveal? false}])
(deftest initialize-permission-addresses-test
(testing "fetches revealed accounts when pending and not joined"
(let [cofx {:db {:communities {community-id {:joined false}}
:communities/my-pending-requests-to-join {community-id [:anything]}}}]
(is (match?
{:fx [[:dispatch
[:communities/get-revealed-accounts community-id
[:communities/do-init-permission-addresses community-id]]]]}
(sut/initialize-permission-addresses cofx [community-id])))))
(testing "fetches revealed accounts when not pending and joined"
(let [cofx {:db {:communities {community-id {:joined true}}
:communities/my-pending-requests-to-join {}}}]
(is (match?
{:fx [[:dispatch
[:communities/get-revealed-accounts community-id
[:communities/do-init-permission-addresses community-id]]]]}
(sut/initialize-permission-addresses cofx [community-id])))))
(testing "does not fetch revealed accounts when not pending and not joined"
(let [cofx {:db {:communities {community-id {:joined false}}
:communities/my-pending-requests-to-join {}}}]
(is (match?
{:fx [[:dispatch [:communities/do-init-permission-addresses community-id]]]}
(sut/initialize-permission-addresses cofx [community-id]))))))
(deftest do-init-permission-addresses-test
(testing "uses already revealed accounts to initialize shareable accounts"
(let [cofx {:db {:wallet {:accounts wallet-accounts}}}
revealed-accounts {"0xC" {:address "0xC"
:airdrop-address? true
:position 1}}
airdrop-address "0xC"
addresses-to-reveal #{"0xC"}]
(is
(match?
{:db (-> (:db cofx)
(assoc-in [:communities/selected-share-all-addresses community-id] false)
(assoc-in [:communities/all-addresses-to-reveal community-id] addresses-to-reveal)
(assoc-in [:communities/all-airdrop-addresses community-id] airdrop-address))
:fx [[:dispatch
[:communities/check-permissions-to-join-community
community-id addresses-to-reveal :based-on-client-selection]]
;; Pre-fetch permissions check so that when first opening the
;; Addresses for Permissions screen the highest permission role is
;; already available and no incorrect information flashes on screen.
[:dispatch
[:communities/check-permissions-to-join-during-selection community-id
addresses-to-reveal]]]}
(sut/do-init-permission-addresses cofx [community-id revealed-accounts])))))
;; Expect to mark all addresses to be revealed and first one to receive
;; airdrops when no addresses were previously revealed.
(testing "handles case where there are no previously revealed addresses"
(let [cofx {:db {:wallet {:accounts wallet-accounts}}}
addresses-to-reveal #{"0xB" "0xC"}
revealed-accounts []]
(is
(match?
{:db (-> (:db cofx)
(assoc-in [:communities/selected-share-all-addresses community-id] false)
(assoc-in [:communities/all-addresses-to-reveal community-id] addresses-to-reveal))
:fx [[:dispatch
[:communities/check-permissions-to-join-community
community-id addresses-to-reveal :based-on-client-selection]]
[:dispatch
[:communities/check-permissions-to-join-during-selection community-id
addresses-to-reveal]]]}
(sut/do-init-permission-addresses cofx [community-id revealed-accounts]))))))
(deftest edit-shared-addresses-test
(testing
"when airdrop address is passed, but addresses to reveal is not, then
fallback to all wallet addresses"
(let [pub-key "abcdef"
revealed-addresses #{"0xB" "0xC"}
cofx {:db {:profile/profile {:public-key pub-key}
:communities/all-addresses-to-reveal {community-id revealed-addresses}}}
airdrop-address "0xB"
actual
(sut/edit-shared-addresses
cofx
[{:community-id community-id
:password password
:airdrop-address airdrop-address
:on-success (fn [new-addresses-to-reveal]
(is (match? revealed-addresses new-addresses-to-reveal)))}])
on-success-wrapper (-> actual :fx first second :on-success)]
(is (match?
{:fx [[:effects.community/edit-shared-addresses
{:community-id community-id
:password password
:pub-key pub-key
:addresses-to-reveal revealed-addresses
:airdrop-address airdrop-address
:on-success fn?
:on-error [:communities/edit-shared-addresses-failure community-id]}]]}
actual))
(on-success-wrapper)))
(testing "when addresses to reveal are passed, but airdrop address is not"
(let [pub-key "abcdef"
cofx {:db {:profile/profile {:public-key pub-key}
:wallet {:accounts wallet-accounts}
:communities/all-addresses-to-reveal {community-id #{"0xB" "0xC"}}}}
actual (sut/edit-shared-addresses
cofx
[{:community-id community-id
:password password
:addresses ["0xC"]
:on-success (fn [new-addresses-to-reveal new-airdrop-address]
(is (= #{"0xC"} new-addresses-to-reveal))
(is (= "0xC" new-airdrop-address)))}])
on-success-wrapper
(-> actual :fx first second :on-success)]
(is (match?
{:fx [[:effects.community/edit-shared-addresses
{:community-id community-id
:password password
:pub-key pub-key
:addresses-to-reveal #{"0xC"}
:airdrop-address "0xC"
:on-success fn?
:on-error [:communities/edit-shared-addresses-failure community-id]}]]}
actual))
(on-success-wrapper))))

View File

@ -15,32 +15,51 @@
(defn view (defn view
[] []
(let [{id :community-id} (rf/sub [:get-screen-params]) (let [{id :community-id} (rf/sub [:get-screen-params])
show-addresses-for-permissions (fn [] {:keys [name color images joined]} (rf/sub [:communities/community id])
(rf/dispatch [:show-bottom-sheet
{:community-id id
:content addresses-for-permissions/view}]))
show-airdrop-addresses (fn []
(rf/dispatch [:show-bottom-sheet
{:community-id id
:content airdrop-addresses/view}]))
navigate-back #(rf/dispatch [:navigate-back])
join-and-go-back (fn []
(rf/dispatch [:password-authentication/show
{:content (fn [] [password-authentication/view])}
{:label (i18n/label :t/join-open-community)
:on-press
#(rf/dispatch
[:communities/request-to-join-with-addresses
{:community-id id :password %}])}])
(navigate-back))]
(fn []
(let [{: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]) 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])
highest-role-text (i18n/label (communities.utils/role->translation-key highest-role-text (i18n/label (communities.utils/role->translation-key
highest-permission-role highest-permission-role
:t/member))] :t/member))
can-edit-addresses? (rf/sub [:communities/can-edit-shared-addresses? id])
navigate-back (rn/use-callback #(rf/dispatch [:navigate-back]))
show-addresses-for-permissions
(rn/use-callback
(fn []
(if can-edit-addresses?
(rf/dispatch [:open-modal :addresses-for-permissions {:community-id id}])
(rf/dispatch [:show-bottom-sheet
{:community-id id
:content (fn [] [addresses-for-permissions/view])}])))
[can-edit-addresses?])
show-airdrop-addresses
(rn/use-callback
(fn []
(if can-edit-addresses?
(rf/dispatch [:open-modal :address-for-airdrop {:community-id id}])
(rf/dispatch [:show-bottom-sheet
{:community-id id
:content (fn [] [airdrop-addresses/view])}])))
[can-edit-addresses?])
confirm-choices
(rn/use-callback
(fn []
(rf/dispatch
[:password-authentication/show
{:content (fn [] [password-authentication/view])}
{:label (i18n/label :t/join-open-community)
:on-press (fn [password]
(rf/dispatch [:communities/request-to-join-with-addresses
{:community-id id :password password}]))}])
(navigate-back)))]
(rn/use-mount
(fn []
(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 {:text-align :left
@ -48,7 +67,9 @@
:on-press navigate-back :on-press navigate-back
:accessibility-label :back-button}] :accessibility-label :back-button}]
[quo/page-top [quo/page-top
{:title (i18n/label :t/request-to-join) {:title (if can-edit-addresses?
(i18n/label :t/edit-shared-addresses)
(i18n/label :t/request-to-join))
:description :context-tag :description :context-tag
:context-tag {:type :community :context-tag {:type :community
:size 24 :size 24
@ -56,21 +77,24 @@
:community-name name}}] :community-name name}}]
[gesture/scroll-view [gesture/scroll-view
[:<> [:<>
(when-not can-edit-addresses?
[quo/text [quo/text
{:style style/section-title {:style style/section-title
:accessibility-label :community-rules-title :accessibility-label :community-rules-title
:weight :semi-bold :weight :semi-bold
:size :paragraph-1} :size :paragraph-1}
(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 {:role highest-role-text}) :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}))
:on-press show-addresses-for-permissions :on-press show-addresses-for-permissions
:description :text :description :text
:action :arrow :action :arrow
:label :preview :label :preview
:label-props {:type :accounts :label-props {:type :accounts
:data selected-accounts} :data revealed-accounts}
:description-props {:text (i18n/label :t/all-addresses)}} :description-props {:text (i18n/label :t/all-addresses)}}
{:title (i18n/label :t/for-airdrops) {:title (i18n/label :t/for-airdrops)
:on-press show-airdrop-addresses :on-press show-airdrop-addresses
@ -80,17 +104,20 @@
:label-props {:type :accounts :label-props {:type :accounts
:data [airdrop-account]} :data [airdrop-account]}
:description-props {:text (:name airdrop-account)}}]}] :description-props {:text (:name airdrop-account)}}]}]
(when-not can-edit-addresses?
[quo/text [quo/text
{:style style/section-title {:style style/section-title
:accessibility-label :community-rules-title :accessibility-label :community-rules-title
:weight :semi-bold :weight :semi-bold
:size :paragraph-1} :size :paragraph-1}
(i18n/label :t/community-rules)] (i18n/label :t/community-rules)])
[community-rules/view id]]] (when-not can-edit-addresses?
[community-rules/view id])]]
(when-not can-edit-addresses?
[rn/view {:style (style/bottom-actions)} [rn/view {:style (style/bottom-actions)}
[quo/slide-button [quo/slide-button
{:size :size-48 {:size :size-48
:track-text (i18n/label :t/slide-to-request-to-join) :track-text (i18n/label :t/slide-to-request-to-join)
:track-icon :i/face-id :track-icon :i/face-id
:customization-color color :customization-color color
:on-complete join-and-go-back}]]])))) :on-complete confirm-choices}]])]))

View File

@ -1,6 +1,7 @@
(ns status-im.contexts.communities.actions.addresses-for-permissions.events (ns status-im.contexts.communities.actions.addresses-for-permissions.events
(:require (:require
[schema.core :as schema] [schema.core :as schema]
[status-im.contexts.communities.utils :as utils]
[taoensso.timbre :as log] [taoensso.timbre :as log]
[utils.re-frame :as rf])) [utils.re-frame :as rf]))
@ -56,3 +57,97 @@
map?]) map?])
(rf/reg-event-fx :communities/get-permissioned-balances-success get-permissioned-balances-success) (rf/reg-event-fx :communities/get-permissioned-balances-success get-permissioned-balances-success)
(rf/reg-event-fx :communities/addresses-for-permissions-cancel-request
(fn [_ [request-id]]
{:fx [[:dispatch [:communities/cancel-request-to-join request-id]]
[:dispatch [:pop-to-root :shell-stack]]
[:dispatch [:hide-bottom-sheet]]]}))
(rf/reg-event-fx :communities/addresses-for-permissions-leave
(fn [_ [community-id]]
{:fx [[:dispatch [:communities/leave community-id]]
[:dispatch [:hide-bottom-sheet]]
[:dispatch [:dismiss-modal :addresses-for-permissions]]
[:dispatch [:pop-to-root :shell-stack]]]}))
(defn check-permissions-to-join-for-selection
[{:keys [db]} [community-id addresses]]
(if (empty? addresses)
;; When there are no addresses we can't just check permissions, otherwise
;; status-go will consider all possible addresses and the user will see the
;; incorrect highest permission role.
{:db (update db :communities/permissions-checks-for-selection dissoc community-id)}
(let [{:keys [checking?]} (get-in db [:communities/permissions-checks-for-selection community-id])]
(when-not checking?
{:db (assoc-in db [:communities/permissions-checks-for-selection community-id :checking?] true)
:fx [[:json-rpc/call
[{:method :wakuext_checkPermissionsToJoinCommunity
:params [{:communityId community-id :addresses addresses}]
:on-success [:communities/check-permissions-to-join-during-selection-success
community-id]
:on-error [:communities/check-permissions-to-join-during-selection-failure
community-id addresses]}]]]}))))
;; This event should be used to check permissions temporarily because it won't
;; mutate the state `:communities/permissions-check` (used by many other
;; screens).
(rf/reg-event-fx :communities/check-permissions-to-join-during-selection
check-permissions-to-join-for-selection)
(rf/reg-event-fx :communities/check-permissions-to-join-during-selection-success
(fn [{:keys [db]} [community-id result]]
{:db (assoc-in db
[:communities/permissions-checks-for-selection community-id]
{:checking? false
:based-on-client-selection? true
:check result})}))
(rf/reg-event-fx :communities/check-permissions-to-join-during-selection-failure
(fn [_ [community-id addresses error]]
(log/error "failed to check permissions for currently selected addresses"
{:event :communities/check-permissions-to-join-during-selection
:community-id community-id
:addresses addresses
:error error})))
(defn set-permissioned-accounts
[{:keys [db]} [community-id addresses-to-reveal]]
(let [addresses-to-reveal (set addresses-to-reveal)
wallet-accounts (utils/sorted-non-watch-only-accounts db)
current-airdrop-address (get-in db [:communities/all-airdrop-addresses community-id])
new-airdrop-address (if (contains? addresses-to-reveal current-airdrop-address)
current-airdrop-address
(->> wallet-accounts
(filter #(contains? addresses-to-reveal (:address %)))
first
:address))]
{:db (-> db
(assoc-in [:communities/all-addresses-to-reveal community-id] addresses-to-reveal)
(assoc-in [:communities/all-airdrop-addresses community-id] new-airdrop-address))
:fx [[:dispatch
[:communities/check-permissions-to-join-community
community-id addresses-to-reveal :based-on-client-selection]]
[:dispatch [:hide-bottom-sheet]]]}))
(rf/reg-event-fx :communities/set-addresses-to-reveal set-permissioned-accounts)
(defn set-share-all-addresses
[{:keys [db]} [community-id new-value]]
(let [current-addresses (get-in db [:communities/all-addresses-to-reveal community-id])
addresses-to-reveal (if new-value
(->> (utils/sorted-non-watch-only-accounts db)
(map :address)
set)
current-addresses)]
{:db (-> db
(assoc-in [:communities/selected-share-all-addresses community-id] new-value)
(assoc-in [:communities/all-addresses-to-reveal community-id] addresses-to-reveal))
;; We should check permissions again because the flag is being enabled and
;; different addresses will be revealed.
:fx [(when new-value
[:dispatch
[:communities/check-permissions-to-join-during-selection
community-id addresses-to-reveal]])]}))
(rf/reg-event-fx :communities/set-share-all-addresses set-share-all-addresses)

View File

@ -1,6 +1,6 @@
(ns status-im.contexts.communities.actions.addresses-for-permissions.events-test (ns status-im.contexts.communities.actions.addresses-for-permissions.events-test
(:require (:require
[cljs.test :refer [deftest is]] [cljs.test :refer [deftest is testing]]
[status-im.contexts.communities.actions.addresses-for-permissions.events :as sut])) [status-im.contexts.communities.actions.addresses-for-permissions.events :as sut]))
(def community-id "0x1") (def community-id "0x1")
@ -13,3 +13,101 @@
:on-success [:communities/get-permissioned-balances-success community-id] :on-success [:communities/get-permissioned-balances-success community-id]
:on-error fn?}]]]} :on-error fn?}]]]}
(sut/get-permissioned-balances cofx [community-id]))))) (sut/get-permissioned-balances cofx [community-id])))))
(deftest set-permissioned-accounts-test
(testing "new accounts contain the current airdrop address"
(let [cofx {:db {:communities/all-addresses-to-reveal
{community-id #{"0xA" "0xB"}}}}
addresses-to-reveal ["0xA"]
expected-addresses-to-reveal (set addresses-to-reveal)]
(is (match?
{:db {:communities/all-addresses-to-reveal
{community-id expected-addresses-to-reveal}}
:fx [[:dispatch
[:communities/check-permissions-to-join-community
community-id expected-addresses-to-reveal :based-on-client-selection]]
[:dispatch [:hide-bottom-sheet]]]}
(sut/set-permissioned-accounts cofx [community-id addresses-to-reveal])))))
(testing "new accounts do not contain the current airdrop address"
(let [cofx
{:db {:communities/all-addresses-to-reveal {community-id #{"0xA" "0xB" "0xC"}}
:communities/all-airdrop-addresses {community-id "0xB"}
:wallet {:accounts {"0xB" {:address "0xB" :position 0}
"0xA" {:address "0xA" :position 1}
"0xC" {:address "0xC" :position 2}}}}}
addresses-to-reveal ["0xA" "0xC"]]
(is (match?
{:db {:communities/all-addresses-to-reveal
{community-id (set addresses-to-reveal)}
:communities/all-airdrop-addresses
{community-id "0xA"}}
:fx [[:dispatch
[:communities/check-permissions-to-join-community
community-id (set addresses-to-reveal) :based-on-client-selection]]
[:dispatch [:hide-bottom-sheet]]]}
(sut/set-permissioned-accounts cofx [community-id addresses-to-reveal]))))))
(deftest set-share-all-addresses-test
(testing "sets flag from false -> true will mark all addresses to be revealed"
(let [cofx {:db
{:wallet
{:accounts {"0xB" {:address "0xB" :position 0}
"0xA" {:address "0xA" :position 1}
"0xC" {:address "0xC" :position 2}}}
:communities/all-addresses-to-reveal {community-id #{"0xA"}}
:communities/selected-share-all-addresses {community-id false}}}
addresses-to-reveal #{"0xA" "0xB" "0xC"}]
(is (match?
{:db {:communities/selected-share-all-addresses {community-id true}
:communities/all-addresses-to-reveal
{community-id addresses-to-reveal}}
:fx [[:dispatch
[:communities/check-permissions-to-join-during-selection
community-id addresses-to-reveal]]]}
(sut/set-share-all-addresses cofx [community-id true])))))
(testing "sets flag from true -> false will not change addresses to be revealed"
(let [cofx {:db {:communities/all-addresses-to-reveal {community-id #{"0xA"}}}}]
(is (match?
{:db {:communities/selected-share-all-addresses {community-id false}
:communities/all-addresses-to-reveal {community-id #{"0xA"}}}
:fx [nil]}
(sut/set-share-all-addresses cofx [community-id false]))))))
(deftest check-permissions-to-join-for-selection-test
(testing "when no addresses are passed don't check permissions"
(let [addresses []
cofx {:db {:foo :bar
:communities/permissions-checks-for-selection
{"0xC" :another-check
community-id :some-check}}}]
(is (match?
{:db {:foo :bar
:communities/permissions-checks-for-selection {"0xC" :another-check}}}
(sut/check-permissions-to-join-for-selection cofx [community-id addresses])))))
(testing "when there are addresses to check permissions and not currently checking"
(let [addresses ["0xA"]
cofx {:db {:communities/permissions-checks-for-selection
{"other-comm-id" {}
community-id {:checking? false}}}}]
(is (match?
{:db {:communities/permissions-checks-for-selection
{"other-comm-id" {}
community-id {:checking? true}}}
:fx [[:json-rpc/call
[{:method :wakuext_checkPermissionsToJoinCommunity
:params [{:communityId community-id :addresses addresses}]
:on-success [:communities/check-permissions-to-join-during-selection-success
community-id]
:on-error [:communities/check-permissions-to-join-during-selection-failure
community-id addresses]}]]]}
(sut/check-permissions-to-join-for-selection cofx [community-id addresses])))))
(testing "when there are addresses to check permissions, but currently checking, then skip the check"
(let [addresses ["0xA"]
cofx {:db {:communities/permissions-checks-for-selection
{"other-comm-id" {}
community-id {:checking? true}}}}]
(is (nil? (sut/check-permissions-to-join-for-selection cofx [community-id addresses]))))))

View File

@ -0,0 +1,6 @@
(ns status-im.contexts.communities.actions.addresses-for-permissions.style)
(def drawer-body
{:padding-top 4
:padding-horizontal 20
:padding-bottom 12})

View File

@ -1,8 +1,13 @@
(ns status-im.contexts.communities.actions.addresses-for-permissions.view (ns status-im.contexts.communities.actions.addresses-for-permissions.view
(:require [quo.core :as quo] (:require
[clojure.string :as string]
[quo.core :as quo]
[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.not-implemented :as not-implemented]
[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]
[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]))
@ -36,44 +41,139 @@
:token (:symbol balance) :token (:symbol balance)
:token-img-src (images-by-symbol sym))))) :token-img-src (images-by-symbol sym)))))
(defn- cancel-join-request-drawer
[community-id]
(let [{:keys [name logo color]} (rf/sub [:communities/for-context-tag community-id])
request-id (rf/sub [:communities/my-pending-request-to-join community-id])]
[:<>
[quo/drawer-top
{:type :context-tag
:context-tag-type :community
:title (i18n/label :t/cancel-request?)
:community-name name
:community-logo logo
:customization-color color}]
[rn/view {:style style/drawer-body}
[quo/text (i18n/label :t/pending-join-request-farewell)]]
[quo/bottom-actions
{:actions :two-actions
:button-one-label (i18n/label :t/confirm-and-cancel)
:button-one-props {:customization-color color
:on-press
(fn []
(rf/dispatch [:communities/addresses-for-permissions-cancel-request
request-id]))}
:button-two-label (i18n/label :t/cancel)
:button-two-props {:type :grey
:on-press #(rf/dispatch [:hide-bottom-sheet])}}]]))
(defn- confirm-discard-drawer
[community-id]
(let [{:keys [name logo color]} (rf/sub [:communities/for-context-tag community-id])]
[:<>
[quo/drawer-top
{:type :context-tag
:context-tag-type :community
:title (i18n/label :t/discard-changes?)
:community-name name
:community-logo logo
:customization-color color}]
[rn/view {:style style/drawer-body}
[quo/text (i18n/label :t/all-changes-will-be-discarded)]]
[quo/bottom-actions
{:actions :two-actions
:button-one-label (i18n/label :t/discard)
:button-one-props {:customization-color color
:on-press
(fn []
(rf/dispatch [:dismiss-modal :addresses-for-permissions])
(rf/dispatch [:hide-bottom-sheet]))}
:button-two-label (i18n/label :t/cancel)
:button-two-props {:type :grey
:on-press #(rf/dispatch [:hide-bottom-sheet])}}]]))
(defn- leave-community-drawer
[community-id outro-message]
(let [{:keys [name logo color]} (rf/sub [:communities/for-context-tag community-id])]
[:<>
[quo/drawer-top
{:type :context-tag
:context-tag-type :community
:title (i18n/label :t/leave-community?)
:community-name name
:community-logo logo
:customization-color color}]
[rn/view {:style style/drawer-body}
[quo/text
(if (string/blank? outro-message)
(i18n/label :t/leave-community-farewell)
outro-message)]]
[quo/bottom-actions
{:actions :two-actions
:button-one-label (i18n/label :t/confirm-and-leave)
:button-one-props {:customization-color color
:on-press
#(rf/dispatch [:communities/addresses-for-permissions-leave community-id])}
:button-two-label (i18n/label :t/cancel)
:button-two-props {:type :grey
:on-press #(rf/dispatch [:hide-bottom-sheet])}}]]))
(defn- account-item (defn- account-item
[{:keys [color address name emoji]} _ _ [{:keys [customization-color address name emoji]} _ _
{:keys [selected-addresses community-id share-all-addresses? community-color]}] [addresses-to-reveal set-addresses-to-reveal community-id community-color share-all-addresses?]]
(let [balances (rf/sub [:communities/permissioned-balances-by-address community-id address]) (let [balances (rf/sub [:communities/permissioned-balances-by-address community-id address])
images-by-symbol (rf/sub [:communities/token-images-by-symbol community-id])] images-by-symbol (rf/sub [:communities/token-images-by-symbol community-id])
checked? (contains? addresses-to-reveal address)
toggle-address (fn []
(let [new-addresses (if checked?
(disj addresses-to-reveal address)
(conj addresses-to-reveal address))]
(set-addresses-to-reveal new-addresses)))]
[quo/account-permissions [quo/account-permissions
{:account {:name name {:account {:name name
:address address :address address
:emoji emoji :emoji emoji
:customization-color color} :customization-color customization-color}
:token-details (balances->components-props balances images-by-symbol) :token-details (when-not share-all-addresses?
:checked? (contains? selected-addresses address) (balances->components-props balances images-by-symbol))
:checked? checked?
:disabled? share-all-addresses? :disabled? share-all-addresses?
:on-change #(rf/dispatch [:communities/toggle-selected-permission-address :on-change toggle-address
address community-id])
:container-style {:margin-bottom 8} :container-style {:margin-bottom 8}
:customization-color community-color}])) :customization-color community-color}]))
(defn view (defn- page-top
[] [{:keys [community-id identical-choices? can-edit-addresses?]}]
(let [{id :community-id} (rf/sub [:get-screen-params]) (let [{:keys [name logo color]} (rf/sub [:communities/for-context-tag community-id])
toggle-share-all-addresses #(rf/dispatch [:communities/toggle-share-all-addresses id]) confirm-discard-changes (rn/use-callback
update-previous-addresses (fn []
(rf/dispatch [:communities/update-previous-permission-addresses id])
(rf/dispatch [:hide-bottom-sheet]))
reset-selected-addresses (fn []
(rf/dispatch [:communities/reset-selected-permission-addresses id])
(rf/dispatch [:hide-bottom-sheet]))]
(rf/dispatch [:communities/get-permissioned-balances id])
(fn [] (fn []
(let [{:keys [name color images]} (rf/sub [:communities/community id]) (if identical-choices?
{:keys [checking? (rf/dispatch [:dismiss-modal :addresses-for-permissions])
highest-permission-role]} (rf/sub [:community/token-gated-overview id]) (rf/dispatch [:show-bottom-sheet
accounts (rf/sub [:wallet/accounts-without-watched-accounts]) {:content (fn [] [confirm-discard-drawer
selected-addresses (rf/sub [:communities/selected-permission-addresses id]) community-id])}])))
share-all-addresses? (rf/sub [:communities/share-all-addresses? id]) [identical-choices?])]
unsaved-address-changes? (rf/sub [:communities/unsaved-address-changes? id])]
[:<> [:<>
(when can-edit-addresses?
[quo/page-nav
{:type :no-title
:icon-name :i/arrow-left
:on-press confirm-discard-changes}])
(if can-edit-addresses?
[quo/page-top
{:title (i18n/label :t/addresses-for-permissions)
:title-accessibility-label :title-label
:description :context-tag
:context-tag {:type :community
:community-logo logo
:community-name name}}]
[quo/drawer-top [quo/drawer-top
{:type :context-tag {:type :context-tag
:title (i18n/label :t/addresses-for-permissions) :title (i18n/label :t/addresses-for-permissions)
@ -82,15 +182,102 @@
:button-icon :i/info :button-icon :i/info
:button-type :grey :button-type :grey
:on-button-press not-implemented/alert :on-button-press not-implemented/alert
:community-logo (get-in images [:thumbnail :uri]) :community-logo logo
:customization-color color}] :customization-color color}])]))
(defn view
[]
(let [{id :community-id} (rf/sub [:get-screen-params])
{:keys [color joined] outro-message :outroMessage} (rf/sub [:communities/community id])
highest-role (rf/sub [:communities/highest-role-for-selection id])
[unmodified-role _] (rn/use-state highest-role)
can-edit-addresses? (rf/sub [:communities/can-edit-shared-addresses? id])
pending? (boolean (rf/sub [:communities/my-pending-request-to-join id]))
wallet-accounts (rf/sub [:wallet/accounts-without-watched-accounts])
unmodified-addresses-to-reveal (rf/sub [:communities/addresses-to-reveal id])
[addresses-to-reveal set-addresses-to-reveal] (rn/use-state unmodified-addresses-to-reveal)
unmodified-flag-share-all-addresses (rf/sub [:communities/share-all-addresses? id])
[flag-share-all-addresses
set-flag-share-all-addresses] (rn/use-state unmodified-flag-share-all-addresses)
identical-choices? (and (= unmodified-addresses-to-reveal addresses-to-reveal)
(= unmodified-flag-share-all-addresses flag-share-all-addresses))
toggle-flag-share-all-addresses
(fn []
(let [new-value (not flag-share-all-addresses)]
(set-flag-share-all-addresses new-value)
(when new-value
(set-addresses-to-reveal (set (map :address wallet-accounts))))))
cancel-join-request
(rn/use-callback
(fn []
(rf/dispatch [:show-bottom-sheet
{:content (fn [] [cancel-join-request-drawer id])}])))
leave-community
(rn/use-callback
(fn []
(rf/dispatch [:show-bottom-sheet
{:content (fn [] [leave-community-drawer id outro-message])}]))
[outro-message])
cancel-selection
(fn []
(rf/dispatch [:communities/check-permissions-to-join-community
id
unmodified-addresses-to-reveal
:based-on-client-selection])
(rf/dispatch [:hide-bottom-sheet]))
confirm-changes
(fn []
(if can-edit-addresses?
(rf/dispatch
[:password-authentication/show
{:content (fn [] [password-authentication/view])}
{:label (i18n/label :t/enter-password)
:on-press (fn [password]
(rf/dispatch
[:communities/edit-shared-addresses
{:community-id id
:password password
:addresses addresses-to-reveal
:on-success (fn []
(rf/dispatch [:dismiss-modal :addresses-for-permissions])
(rf/dispatch [:hide-bottom-sheet]))}]))}])
(do
(rf/dispatch [:communities/set-share-all-addresses id flag-share-all-addresses])
(rf/dispatch [:communities/set-addresses-to-reveal id addresses-to-reveal]))))]
(rn/use-mount
(fn []
(when-not flag-share-all-addresses
(rf/dispatch [:communities/get-permissioned-balances id]))))
(rn/use-effect
(fn []
(rf/dispatch [:communities/check-permissions-to-join-during-selection id addresses-to-reveal]))
[id addresses-to-reveal])
[:<>
[page-top
{:community-id id
:identical-choices? identical-choices?
:can-edit-addresses? can-edit-addresses?}]
[gesture/flat-list [gesture/flat-list
{:render-fn account-item {:render-fn account-item
:render-data {:selected-addresses selected-addresses :render-data [addresses-to-reveal
:community-id id set-addresses-to-reveal
:share-all-addresses? share-all-addresses? id
:community-color color} color
flag-share-all-addresses]
:header [quo/category :header [quo/category
{:list-type :settings {:list-type :settings
:data [{:title :data [{:title
@ -98,34 +285,72 @@
:t/share-all-current-and-future-addresses) :t/share-all-current-and-future-addresses)
:action :selector :action :selector
:action-props :action-props
{:on-change toggle-share-all-addresses {:on-change toggle-flag-share-all-addresses
:customization-color color :customization-color color
:checked? share-all-addresses?}}] :checked? flag-share-all-addresses}}]
:container-style {:padding-bottom 16 :padding-horizontal 0}}] :container-style {:padding-bottom 16 :padding-horizontal 0}}]
:content-container-style {:padding-horizontal 20} :content-container-style {:padding-horizontal 20}
:key-fn :address :key-fn :address
:data accounts}] :data wallet-accounts}]
[quo/bottom-actions [quo/bottom-actions
{:actions :two-actions (cond-> {:role (role-keyword highest-role)
:button-one-label (i18n/label :t/confirm-changes) :description (if highest-role
:top
:top-error)
:button-one-props {:customization-color color :button-one-props {:customization-color color
:disabled? (or checking? :on-press confirm-changes
(empty? selected-addresses) :disabled? (or (empty? addresses-to-reveal)
(not highest-permission-role) (not highest-role)
(not unsaved-address-changes?)) identical-choices?)}}
:on-press update-previous-addresses} can-edit-addresses?
(->
(assoc :actions :one-action
:button-one-label (cond
(and pending? (not highest-role))
(i18n/label :t/cancel-request)
(and joined (not highest-role))
(i18n/label :t/leave-community)
:else
(i18n/label :t/confirm-changes))
:description-top-text (cond
(and pending? highest-role)
(i18n/label :t/eligible-to-join-as)
(and joined (= highest-role unmodified-role))
(i18n/label :t/you-are-a)
(and joined (not= highest-role unmodified-role))
(i18n/label :t/you-will-be-a))
:error-message (cond
(and pending? (not highest-role))
(i18n/label :t/community-join-requirements-not-met)
(and joined (not highest-role))
(i18n/label :t/membership-requirements-not-met)))
(update :button-one-props
merge
(cond (and pending? (not highest-role))
{:type :danger
:disabled? false
:on-press cancel-join-request}
(and joined (not highest-role))
{:type :danger
:disabled? false
:on-press leave-community})))
(not can-edit-addresses?)
(assoc :actions :two-actions
:button-one-label (i18n/label :t/confirm-changes)
:button-two-label (i18n/label :t/cancel) :button-two-label (i18n/label :t/cancel)
:button-two-props {:type :grey :button-two-props {:type :grey
:on-press reset-selected-addresses} :on-press cancel-selection}
:description (if (or (empty? selected-addresses)
(not highest-permission-role))
:top-error
:top)
:role (when-not checking? (role-keyword highest-permission-role))
:error-message (cond :error-message (cond
(empty? selected-addresses) (empty? addresses-to-reveal)
(i18n/label :t/no-addresses-selected) (i18n/label :t/no-addresses-selected)
(not highest-permission-role) (not highest-role)
(i18n/label :t/addresses-dont-contain-tokens-needed))}]])))) (i18n/label :t/addresses-dont-contain-tokens-needed))))]]))

View File

@ -0,0 +1,9 @@
(ns status-im.contexts.communities.actions.airdrop-addresses.events
(:require
[utils.re-frame :as rf]))
(defn set-airdrop-address
[{:keys [db]} [community-id airdrop-address]]
{:db (assoc-in db [:communities/all-airdrop-addresses community-id] airdrop-address)})
(rf/reg-event-fx :communities/set-airdrop-address set-airdrop-address)

View File

@ -0,0 +1,18 @@
(ns status-im.contexts.communities.actions.airdrop-addresses.events-test
(:require
[cljs.test :refer [deftest is testing]]
[status-im.contexts.communities.actions.airdrop-addresses.events :as sut]))
(def community-id "community-id")
(deftest set-airdrop-address-test
(testing "updates all reveal? flags"
(let [new-airdrop-address "0xA"
cofx {:db {:communities/all-airdrop-addresses
{"another-community" "0xF"
community-id "0xB"}}}]
(is (match?
(assoc-in cofx
[:db :communities/all-airdrop-addresses community-id]
new-airdrop-address)
(sut/set-airdrop-address cofx [community-id new-airdrop-address]))))))

View File

@ -1,29 +1,68 @@
(ns status-im.contexts.communities.actions.airdrop-addresses.view (ns status-im.contexts.communities.actions.airdrop-addresses.view
(:require (:require
[quo.core :as quo] [quo.core :as quo]
[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.not-implemented :as not-implemented]
[status-im.common.password-authentication.view :as password-authentication]
[status-im.contexts.communities.actions.airdrop-addresses.style :as style] [status-im.contexts.communities.actions.airdrop-addresses.style :as style]
[utils.i18n :as i18n] [utils.i18n :as i18n]
[utils.re-frame :as rf])) [utils.re-frame :as rf]))
(defn- render-item (defn- account-item
[item _ _ [airdrop-address community-id]] [{:keys [address emoji] :as account}
_ _
[community-id airdrop-address can-edit-addresses?]]
(let [airdrop-address? (= address airdrop-address)
on-press
(when-not airdrop-address?
(fn []
(if can-edit-addresses?
(rf/dispatch
[:password-authentication/show
{:content (fn [] [password-authentication/view])}
{:label (i18n/label :t/enter-password)
:on-press (fn [password]
(rf/dispatch
[:communities/edit-shared-addresses
{:community-id community-id
:password password
:airdrop-address address
:on-success (fn []
(rf/dispatch [:dismiss-modal :address-for-airdrop])
(rf/dispatch [:hide-bottom-sheet]))}]))}])
(do
(rf/dispatch [:communities/set-airdrop-address community-id address])
(rf/dispatch [:hide-bottom-sheet])))))]
[quo/account-item [quo/account-item
{:account-props item {:account-props account
:state (when (= airdrop-address (:address item)) :selected) :emoji emoji
:on-press (fn [] :state (if airdrop-address? :selected :default)
(rf/dispatch [:communities/set-airdrop-address (:address item) community-id]) :on-press on-press}]))
(rf/dispatch [:hide-bottom-sheet]))
:emoji (:emoji item)}])
(defn view (defn view
[] []
(let [{id :community-id} (rf/sub [:get-screen-params]) (let [{id :community-id} (rf/sub [:get-screen-params])
{:keys [name images color]} (rf/sub [:communities/community id]) {:keys [name logo color]} (rf/sub [:communities/for-context-tag id])
selected-accounts (rf/sub [:communities/selected-permission-accounts id]) accounts (rf/sub [:communities/accounts-to-reveal id])
airdrop-address (rf/sub [:communities/airdrop-address id])] airdrop-address (rf/sub [:communities/airdrop-address id])
can-edit-addresses? (rf/sub [:communities/can-edit-shared-addresses? id])
go-back (rn/use-callback #(rf/dispatch [:dismiss-modal :address-for-airdrop]))]
[:<> [:<>
(when can-edit-addresses?
[quo/page-nav
{:type :no-title
:icon-name :i/arrow-left
:on-press go-back}])
(if can-edit-addresses?
[quo/page-top
{:title (i18n/label :t/airdrop-addresses)
:title-accessibility-label :title-label
:description :context-tag
:context-tag {:type :community
:community-logo logo
:community-name name}}]
[quo/drawer-top [quo/drawer-top
{:type :context-tag {:type :context-tag
:context-tag-type :community :context-tag-type :community
@ -31,11 +70,12 @@
:community-name name :community-name name
:button-icon :i/info :button-icon :i/info
:on-button-press not-implemented/alert :on-button-press not-implemented/alert
:community-logo (get-in images [:thumbnail :uri]) :community-logo logo
:customization-color color}] :customization-color color}])
[gesture/flat-list [gesture/flat-list
{:data selected-accounts {:data accounts
:render-fn render-item :render-fn account-item
:render-data [airdrop-address id] :render-data [id airdrop-address can-edit-addresses?]
:content-container-style style/account-list-container :content-container-style style/account-list-container
:key-fn :address}]])) :key-fn :address}]]))

View File

@ -7,6 +7,7 @@
[status-im.contexts.communities.actions.leave.view :as leave-menu] [status-im.contexts.communities.actions.leave.view :as leave-menu]
[status-im.contexts.communities.actions.see-rules.view :as see-rules] [status-im.contexts.communities.actions.see-rules.view :as see-rules]
[status-im.contexts.communities.actions.token-gating.view :as token-gating] [status-im.contexts.communities.actions.token-gating.view :as token-gating]
[status-im.feature-flags :as ff]
[utils.i18n :as i18n] [utils.i18n :as i18n]
[utils.re-frame :as rf])) [utils.re-frame :as rf]))
@ -41,6 +42,15 @@
{:content (fn [] [token-gating/token-requirements id])}]) {:content (fn [] [token-gating/token-requirements id])}])
:label (i18n/label :t/view-token-gating)}) :label (i18n/label :t/view-token-gating)})
(defn edit-shared-addresses
[id]
{:icon :i/wallet
:right-icon :i/chevron-right
:accessibility-label :edit-shared-addresses
:on-press (fn []
(rf/dispatch [:open-modal :community-account-selection {:community-id id}]))
:label (i18n/label :t/edit-shared-addresses)})
(defn mark-as-read (defn mark-as-read
[id] [id]
{:icon :i/up-to-date {:icon :i/up-to-date
@ -119,28 +129,33 @@
request-id])}])}) request-id])}])})
(defn not-joined-options (defn not-joined-options
[id token-gated?] [id token-gated? pending?]
[[(when-not token-gated? (view-members id)) [[(when-not token-gated? (view-members id))
(when-not token-gated? (view-rules id)) (when-not token-gated? (view-rules id))
(invite-contacts id) (invite-contacts id)
(when token-gated? (view-token-gating id)) (when token-gated? (view-token-gating id))
(when (and pending? (ff/enabled? ::ff/community.edit-account-selection))
(edit-shared-addresses id))
(show-qr id) (show-qr id)
(share-community id)]]) (share-community id)]])
(defn join-request-sent-options (defn join-request-sent-options
[id token-gated? request-id] [id token-gated? request-id]
[(conj (first (not-joined-options id token-gated?)) [(conj (first (not-joined-options id token-gated? request-id))
(assoc (cancel-request-to-join id request-id) :add-divider? true))]) (assoc (cancel-request-to-join id request-id) :add-divider? true))])
(defn banned-options (defn banned-options
[id token-gated?] [id token-gated?]
(not-joined-options id token-gated?)) (let [pending? false]
(not-joined-options id token-gated? pending?)))
(defn joined-options (defn joined-options
[id token-gated? muted? muted-till] [id token-gated? muted? muted-till]
[[(view-members id) [[(view-members id)
(view-rules id) (view-rules id)
(when token-gated? (view-token-gating id)) (when token-gated? (view-token-gating id))
(when (ff/enabled? ::ff/community.edit-account-selection)
(edit-shared-addresses id))
(mark-as-read id) (mark-as-read id)
(mute-community id muted? muted-till) (mute-community id muted? muted-till)
(community-notification-settings id) (community-notification-settings id)
@ -171,7 +186,7 @@
joined (joined-options id token-permissions muted muted-till) joined (joined-options id token-permissions muted muted-till)
request-id (join-request-sent-options id token-permissions request-id) request-id (join-request-sent-options id token-permissions request-id)
banList (banned-options id token-permissions) banList (banned-options id token-permissions)
:else (not-joined-options id token-permissions)))) :else (not-joined-options id token-permissions request-id))))
(defn community-options-bottom-sheet (defn community-options-bottom-sheet
[id] [id]

View File

@ -29,10 +29,11 @@
{:type :positive {:type :positive
:text (i18n/label :t/left-community {:community community-name})}]] :text (i18n/label :t/left-community {:community community-name})}]]
[:dispatch [:activity-center.notifications/fetch-unread-count]] [:dispatch [:activity-center.notifications/fetch-unread-count]]
[:dispatch [:hide-bottom-sheet]]
[:dispatch [:navigate-back]]]}))) [:dispatch [:navigate-back]]]})))
(rf/reg-event-fx :communities/leave (rf/reg-event-fx :communities/leave
(fn [{:keys [db]} [community-id]] (fn [{:keys [db]} [community-id on-success]]
(let [community-chat-ids (map #(str community-id %) (let [community-chat-ids (map #(str community-id %)
(keys (get-in db [:communities community-id :chats])))] (keys (get-in db [:communities community-id :chats])))]
{:effects/push-notifications-clear-message-notifications community-chat-ids {:effects/push-notifications-clear-message-notifications community-chat-ids
@ -41,5 +42,8 @@
[{:method "wakuext_leaveCommunity" [{:method "wakuext_leaveCommunity"
:params [community-id] :params [community-id]
:js-response true :js-response true
:on-success #(rf/dispatch [:communities/left %]) :on-success (fn [^js response]
(when (fn? on-success)
(on-success))
(rf/dispatch [:communities/left response]))
:on-error #(log/error "failed to leave community" community-id %)}]}))) :on-error #(log/error "failed to leave community" community-id %)}]})))

View File

@ -9,10 +9,11 @@
[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.accounts-selection.events
status-im.contexts.communities.actions.addresses-for-permissions.events status-im.contexts.communities.actions.addresses-for-permissions.events
status-im.contexts.communities.actions.airdrop-addresses.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.contexts.communities.utils :as utils]
[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]
@ -29,13 +30,10 @@
{: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 [[:dispatch [:communities/initialize-permission-addresses id]] :fx [(when (not joined)
(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]])]}))))
(when joined
[:dispatch [:communities/get-revealed-accounts id]])]}))))
(rf/reg-event-fx :communities/handle-community handle-community) (rf/reg-event-fx :communities/handle-community handle-community)
@ -157,97 +155,6 @@
:on-success #(rf/dispatch [:communities/fetched-collapsed-categories-success %]) :on-success #(rf/dispatch [:communities/fetched-collapsed-categories-success %])
:on-error #(log/error "failed to fetch collapsed community categories" %)}]})) :on-error #(log/error "failed to fetch collapsed community categories" %)}]}))
(defn initialize-permission-addresses
[{:keys [db]} [community-id]]
(when community-id
(let [accounts (utils/sorted-non-watch-only-accounts db)
addresses (set (map :address accounts))]
{:db (update-in db
[:communities community-id]
assoc
:previous-share-all-addresses? true
:share-all-addresses? true
:previous-permission-addresses addresses
:selected-permission-addresses addresses
:airdrop-address (:address (first accounts)))})))
(rf/reg-event-fx :communities/initialize-permission-addresses
initialize-permission-addresses)
(defn update-previous-permission-addresses
[{:keys [db]} [community-id]]
(when community-id
(let [accounts (utils/sorted-non-watch-only-accounts db)
selected-permission-addresses (get-in db
[:communities community-id
:selected-permission-addresses])
selected-accounts (filter #(contains? selected-permission-addresses (:address %))
accounts)
current-airdrop-address (get-in db [:communities community-id :airdrop-address])
share-all-addresses? (get-in db [:communities community-id :share-all-addresses?])]
{:db (update-in db
[:communities community-id]
assoc
:previous-share-all-addresses? share-all-addresses?
:previous-permission-addresses selected-permission-addresses
:airdrop-address (if (contains? selected-permission-addresses
current-airdrop-address)
current-airdrop-address
(:address (first selected-accounts))))})))
(rf/reg-event-fx :communities/update-previous-permission-addresses
update-previous-permission-addresses)
(defn toggle-selected-permission-address
[{:keys [db]} [address community-id]]
(let [selected-permission-addresses
(get-in db [:communities community-id :selected-permission-addresses])
updated-selected-permission-addresses
(if (contains? selected-permission-addresses address)
(disj selected-permission-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
toggle-selected-permission-address)
(defn toggle-share-all-addresses
[{:keys [db]} [community-id]]
(let [share-all-addresses? (get-in db [:communities community-id :share-all-addresses?])
next-share-all-addresses? (not share-all-addresses?)
accounts (utils/sorted-non-watch-only-accounts db)
addresses (set (map :address accounts))]
{:db (update-in db
[:communities community-id]
assoc
:share-all-addresses? next-share-all-addresses?
:selected-permission-addresses addresses)
:fx [(when (and community-id next-share-all-addresses?)
[:dispatch
[:communities/check-permissions-to-join-community community-id
addresses :based-on-client-selection]])]}))
(rf/reg-event-fx :communities/toggle-share-all-addresses
toggle-share-all-addresses)
(rf/reg-event-fx :communities/reset-selected-permission-addresses
(fn [{:keys [db]} [community-id]]
(when community-id
{:db (update-in db
[:communities community-id]
assoc
:selected-permission-addresses
(get-in db [:communities community-id :previous-permission-addresses])
:share-all-addresses?
(get-in db [:communities community-id :previous-share-all-addresses?]))
:fx [[:dispatch [:communities/check-permissions-to-join-community community-id]]]})))
(rf/reg-event-fx :communities/get-community-channel-share-data (rf/reg-event-fx :communities/get-community-channel-share-data
(fn [_ [chat-id on-success]] (fn [_ [chat-id on-success]]
(let [{:keys [community-id channel-id]} (data-store.chats/decode-chat-id chat-id)] (let [{:keys [community-id channel-id]} (data-store.chats/decode-chat-id chat-id)]
@ -286,10 +193,6 @@
:url %}])] :url %}])]
{:fx [[:dispatch [:communities/get-community-channel-share-data chat-id on-success]]]}))) {:fx [[:dispatch [:communities/get-community-channel-share-data chat-id on-success]]]})))
(rf/reg-event-fx :communities/set-airdrop-address
(fn [{:keys [db]} [address community-id]]
{:db (assoc-in db [:communities community-id :airdrop-address] address)}))
(defn community-fetched (defn community-fetched
[{:keys [db]} [community-id community]] [{:keys [db]} [community-id community]]
(when community (when community
@ -425,16 +328,17 @@
[:dispatch [:chat/navigate-to-chat chat-id]])]}))) [:dispatch [:chat/navigate-to-chat chat-id]])]})))
(defn get-revealed-accounts (defn get-revealed-accounts
[{:keys [db]} [community-id]] [{:keys [db]} [community-id on-success]]
(let [{:keys [joined fetching-revealed-accounts] (let [{:keys [joined fetching-revealed-accounts]
:as community} (get-in db [:communities community-id])] :as community} (get-in db [:communities community-id])
(when (and community joined (not fetching-revealed-accounts)) pending? (get-in db [:communities/my-pending-requests-to-join community-id])]
(when (and community (or pending? joined) (not fetching-revealed-accounts))
{:db (assoc-in db [:communities community-id :fetching-revealed-accounts] true) {:db (assoc-in db [:communities community-id :fetching-revealed-accounts] true)
:json-rpc/call :json-rpc/call
[{:method "wakuext_getRevealedAccounts" [{:method "wakuext_getRevealedAccounts"
:params [community-id (get-in db [:profile/profile :public-key])] :params [community-id (get-in db [:profile/profile :public-key])]
:js-response true :js-response true
:on-success [:communities/get-revealed-accounts-success community-id] :on-success [:communities/get-revealed-accounts-success community-id on-success]
:on-error (fn [err] :on-error (fn [err]
(log/error {:message "failed to fetch revealed accounts" (log/error {:message "failed to fetch revealed accounts"
:community-id community-id :community-id community-id
@ -448,19 +352,22 @@
[:catn [:catn
[:cofx :schema.re-frame/cofx] [:cofx :schema.re-frame/cofx]
[:args [:args
[:schema [:catn [:community-id [:? :string]]]]]] [:schema
[:catn
[:community-id [:? :string]]
[:on-success [:? :schema.re-frame/event]]]]]]
[:maybe [:maybe
[:map [:map
[:db map?] [:db map?]
[:json-rpc/call :schema.common/rpc-call]]]]) [:json-rpc/call :schema.common/rpc-call]]]])
(rf/reg-event-fx :communities/get-revealed-accounts-success (rf/reg-event-fx :communities/get-revealed-accounts-success
(fn [{:keys [db]} [community-id revealed-accounts-js]] (fn [{:keys [db]} [community-id on-success revealed-accounts-js]]
(when-let [community (get-in db [:communities community-id])] (when-let [community (get-in db [:communities community-id])]
(let [revealed-accounts (let [revealed-accounts
(reduce (reduce
(fn [acc {:keys [address] :as revealed-account}] (fn [acc {:keys [address] :as revealed-account}]
(assoc acc address (dissoc revealed-account :address))) (assoc acc address revealed-account))
{} {}
(data-store.communities/<-revealed-accounts-rpc revealed-accounts-js)) (data-store.communities/<-revealed-accounts-rpc revealed-accounts-js))
@ -468,7 +375,9 @@
(-> community (-> community
(assoc :revealed-accounts revealed-accounts) (assoc :revealed-accounts revealed-accounts)
(dissoc :fetching-revealed-accounts))] (dissoc :fetching-revealed-accounts))]
{:db (assoc-in db [:communities community-id] community-with-revealed-accounts)})))) {:db (assoc-in db [:communities community-id] community-with-revealed-accounts)
:fx [(when (vector? on-success)
[:dispatch (conj on-success revealed-accounts)])]}))))
(rf/reg-event-fx :communities/get-revealed-accounts-failed (rf/reg-event-fx :communities/get-revealed-accounts-failed
(fn [{:keys [db]} [community-id]] (fn [{:keys [db]} [community-id]]

View File

@ -7,99 +7,6 @@
(def community-id "community-id") (def community-id "community-id")
(deftest initialize-permission-addresses-test
(let [initial-db {:db {:wallet {:accounts {"0x1" {:address "0x1"
:position 0}
"0x2" {:address "0x2"
:position 1}}}}}
expected-db {:db (update-in (:db initial-db)
[:communities community-id]
assoc
:share-all-addresses? true
:previous-share-all-addresses? true
:previous-permission-addresses #{"0x1" "0x2"}
:selected-permission-addresses #{"0x1" "0x2"}
:airdrop-address "0x1")}]
(is (match? expected-db (events/initialize-permission-addresses initial-db [community-id])))))
(deftest toggle-selected-permission-address-test
(let [initial-db {:db {:communities {community-id {:selected-permission-addresses #{"0x1" "0x2"}}}}}]
(is (match? {:db {:communities {community-id {:selected-permission-addresses #{"0x1"}}}}}
(events/toggle-selected-permission-address initial-db ["0x2" community-id])))
(is (match? {:db {:communities {community-id {:selected-permission-addresses #{"0x1" "0x2" "0x3"}}}}}
(events/toggle-selected-permission-address initial-db ["0x3" community-id])))))
(deftest update-previous-permission-addresses-test
(let [wallet {:accounts {"0x1" {:address "0x1"
:position 0}
"0x2" {:address "0x2"
:position 1}
"0x3" {:address "0x3"
:position 2}}}]
(let [initial-db {:db {:wallet wallet
:communities {community-id {:previous-permission-addresses #{"0x1" "0x2"}
:selected-permission-addresses #{"0x1" "0x2"
"0x3"}
:share-all-addresses? true
:previous-share-all-addresses? false
:airdrop-address "0x1"}}}}
expected-db {:db {:wallet wallet
:communities {community-id {:previous-permission-addresses #{"0x1" "0x2"
"0x3"}
:selected-permission-addresses #{"0x1" "0x2"
"0x3"}
:share-all-addresses? true
:previous-share-all-addresses? true
:airdrop-address "0x1"}}}}]
(is
(match? expected-db
(events/update-previous-permission-addresses initial-db [community-id]))))
(let [initial-db {:db {:wallet wallet
:communities {community-id {:previous-permission-addresses #{"0x1" "0x2"}
:selected-permission-addresses #{"0x2" "0x3"}
:airdrop-address "0x1"}}}}
expected-db {:db {:wallet wallet
:communities {community-id {:previous-permission-addresses #{"0x2" "0x3"}
:selected-permission-addresses #{"0x2" "0x3"}
:airdrop-address "0x2"}}}}]
(is
(match? expected-db
(events/update-previous-permission-addresses initial-db [community-id]))))))
(deftest toggle-share-all-addresses-test
(let [initial-db {:db {:wallet {:accounts {"0x1" {:address "0x1"
:position 0}
"0x2" {:address "0x2"
:position 1}}}
:communities {community-id {:share-all-addresses? true
:previous-share-all-addresses? true
:previous-permission-addresses #{"0x1" "0x2"}
:selected-permission-addresses #{"0x1" "0x2"}
:airdrop-address "0x1"}}}}
expected-db (update-in initial-db
[:db :communities community-id]
assoc
:previous-share-all-addresses? true
:share-all-addresses?
false)]
(is (match? expected-db (events/toggle-share-all-addresses initial-db [community-id]))))
(let [initial-db {:db {:wallet {:accounts {"0x1" {:address "0x1"
:position 0}
"0x2" {:address "0x2"
:position 1}}}
:communities {community-id {:share-all-addresses? false
:previous-share-all-addresses? false
:previous-permission-addresses #{"0x1"}
:selected-permission-addresses #{"0x1"}
:airdrop-address "0x1"}}}}
expected-db (update-in initial-db
[:db :communities community-id]
assoc
:selected-permission-addresses #{"0x1" "0x2"}
:previous-share-all-addresses? false
:share-all-addresses? true)]
(is (match? expected-db (events/toggle-share-all-addresses initial-db [community-id])))))
(deftest fetch-community (deftest fetch-community
(testing "with community id" (testing "with community id"
(testing "update fetching indicator in db" (testing "update fetching indicator in db"
@ -243,12 +150,14 @@
(is (match? (is (match?
nil nil
(events/get-revealed-accounts {:db {:communities {community-id community}}} [community-id])))) (events/get-revealed-accounts {:db {:communities {community-id community}}} [community-id]))))
(testing "given a already :fetching-revealed-accounts community" (testing "given a already :fetching-revealed-accounts community"
(is (match? (is (match?
nil nil
(events/get-revealed-accounts (events/get-revealed-accounts
{:db {:communities {community-id (assoc community :fetching-revealed-accounts true)}}} {:db {:communities {community-id (assoc community :fetching-revealed-accounts true)}}}
[community-id])))) [community-id]))))
(testing "given joined community" (testing "given joined community"
(let [community (assoc community :joined true) (let [community (assoc community :joined true)
db {:communities {community-id community} db {:communities {community-id community}
@ -258,7 +167,26 @@
(:db effects))) (:db effects)))
(is (match? {:method "wakuext_getRevealedAccounts" (is (match? {:method "wakuext_getRevealedAccounts"
:params [community-id "profile-public-key"]} :params [community-id "profile-public-key"]}
(-> effects :json-rpc/call first (select-keys [:method :params])))))))) (-> effects :json-rpc/call first (select-keys [:method :params]))))))
(testing "given there is a pending request"
(let [pub-key "profile-public-key"
community (assoc community :joined false)
db {:communities {community-id community}
:profile/profile {:public-key pub-key}
:communities/my-pending-requests-to-join {community-id [:anything]}}
on-success [:some-event-id]
actual (events/get-revealed-accounts {:db db} [community-id on-success])
expected {:db (assoc-in db
[:communities community-id :fetching-revealed-accounts]
true)
:json-rpc/call [{:method "wakuext_getRevealedAccounts"
:params [community-id pub-key]
:js-response true
:on-success [:communities/get-revealed-accounts-success
community-id on-success]
:on-error fn?}]}]
(is (match? expected actual))))))
(deftest handle-community (deftest handle-community
(let [community {:id community-id :clock 2}] (let [community {:id community-id :clock 2}]
@ -267,24 +195,22 @@
(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 [:communities/initialize-permission-addresses community-id]] [[:dispatch [:chat.ui/spectate-community 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))))))
(testing "given a joined community" (testing "given a joined community"
(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/initialize-permission-addresses community-id]] [[:dispatch [:communities/check-permissions-to-join-community community-id]]]
[:dispatch [:communities/check-permissions-to-join-community community-id]]
[:dispatch [:communities/get-revealed-accounts 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 [:communities/initialize-permission-addresses community-id]] [[:dispatch [:chat.ui/spectate-community 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

@ -4,8 +4,6 @@
[utils.i18n :as i18n] [utils.i18n :as i18n]
[utils.re-frame :as rf])) [utils.re-frame :as rf]))
(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 based-on-client-selection? result]] (fn [{:keys [db]} [community-id based-on-client-selection? result]]
(let [token-permissions-check (cond-> result (let [token-permissions-check (cond-> result
@ -118,41 +116,19 @@
:category-id category-id :category-id category-id
:collapse? collapse?})}]})) :collapse? collapse?})}]}))
(defn request-to-join-with-signatures-and-addresses
[{:keys [db]} [community-id signatures]]
(let [{:keys [airdrop-address selected-permission-addresses]} (get-in db [:communities community-id])]
{:fx [[:json-rpc/call
[{:method "wakuext_requestToJoinCommunity"
:params [{:communityId community-id
:signatures signatures
:addressesToReveal selected-permission-addresses
:airdropAddress airdrop-address}]
:js-response true
:on-success [:communities/requested-to-join]
:on-error [:communities/requested-to-join-error community-id]}]]]}))
(rf/reg-event-fx :communities/request-to-join-with-signatures-and-addresses
request-to-join-with-signatures-and-addresses)
(defn sign-data-with-addresses
[_ [community-id password sign-params]]
{:fx [[:json-rpc/call
[{:method "wakuext_signData"
:params [(map #(assoc % :password password) sign-params)]
:on-success [:communities/request-to-join-with-signatures-and-addresses community-id]
:on-error [:communities/requested-to-join-error community-id]}]]]})
(rf/reg-event-fx :communities/sign-data-with-addresses sign-data-with-addresses)
(defn request-to-join-with-addresses (defn request-to-join-with-addresses
[{:keys [db]} [{:keys [db]}
[{:keys [community-id password]}]] [{:keys [community-id password]}]]
(let [pub-key (get-in db [:profile/profile :public-key]) (let [pub-key (get-in db [:profile/profile :public-key])
addresses-to-reveal (get-in db [:communities community-id :selected-permission-addresses])] addresses-to-reveal (get-in db [:communities/all-addresses-to-reveal community-id])
{:fx [[:json-rpc/call airdrop-address (get-in db [:communities/all-airdrop-addresses community-id])]
[{:method "wakuext_generateJoiningCommunityRequestsForSigning" {:fx [[:effects.community/request-to-join
:params [pub-key community-id addresses-to-reveal] {:community-id community-id
:on-success [:communities/sign-data-with-addresses community-id password] :password password
:on-error [:communities/requested-to-join-error community-id]}]]]})) :pub-key pub-key
:addresses-to-reveal addresses-to-reveal
:airdrop-address airdrop-address
:on-success [:communities/requested-to-join]
:on-error [:communities/requested-to-join-error community-id]}]]}))
(rf/reg-event-fx :communities/request-to-join-with-addresses request-to-join-with-addresses) (rf/reg-event-fx :communities/request-to-join-with-addresses request-to-join-with-addresses)

View File

@ -120,7 +120,7 @@
[id color] [id color]
[quo/button [quo/button
{:on-press (if config/community-accounts-selection-enabled? {:on-press (if config/community-accounts-selection-enabled?
#(rf/dispatch [:open-modal :community-account-selection #(rf/dispatch [:open-modal :community-account-selection-sheet
{:community-id id}]) {:community-id id}])
#(rf/dispatch [:open-modal :community-requests-to-join {:id id}])) #(rf/dispatch [:open-modal :community-requests-to-join {:id id}]))
:accessibility-label :show-request-to-join-screen-button :accessibility-label :show-request-to-join-screen-button
@ -168,7 +168,7 @@
:padding? true}] :padding? true}]
[quo/button [quo/button
{:on-press (if config/community-accounts-selection-enabled? {:on-press (if config/community-accounts-selection-enabled?
#(rf/dispatch [:open-modal :community-account-selection #(rf/dispatch [:open-modal :community-account-selection-sheet
{:community-id id}]) {:community-id id}])
#(rf/dispatch [:open-modal :community-requests-to-join {:id id}])) #(rf/dispatch [:open-modal :community-requests-to-join {:id id}]))
:accessibility-label :join-community-button :accessibility-label :join-community-button

View File

@ -63,6 +63,10 @@
{:key :admin} {:key :admin}
{:key :member}]}) {:key :member}]})
(def description-top-descriptor
{:key :description-top-text
:type :text})
(def description-descriptor (def description-descriptor
{:key :description-text {:key :description-text
:type :text}) :type :text})
@ -76,6 +80,7 @@
(let [state (reagent/atom {:actions :two-actions (let [state (reagent/atom {:actions :two-actions
:description :bottom :description :bottom
:description-text description :description-text description
:description-top-text "Eligible to join as"
:error-message "Error message" :error-message "Error message"
:button-one-label button-one :button-one-label button-one
:button-two-label button-two :button-two-label button-two
@ -89,12 +94,15 @@
(fn [] (fn []
[preview/preview-container [preview/preview-container
{:state state {:state state
:descriptor (merge descriptor :descriptor (cond-> descriptor
(case (:description @state) (= (:description @state) :top)
:top role-descriptor (conj role-descriptor description-top-descriptor)
:bottom description-descriptor
:top-error error-descriptor (= (:description @state) :bottom)
nil)) (conj description-descriptor)
(= (:description @state) :top-error)
(conj error-descriptor))
:blur? (:blur? @state) :blur? (:blur? @state)
:show-blur-background? true :show-blur-background? true
:blur-dark-only? true :blur-dark-only? true

View File

@ -12,8 +12,10 @@
(reagent/atom (reagent/atom
{::wallet.edit-default-keypair true {::wallet.edit-default-keypair true
::wallet.bridge-token (enabled-in-env? :FLAG_BRIDGE_TOKEN_ENABLED) ::wallet.bridge-token (enabled-in-env? :FLAG_BRIDGE_TOKEN_ENABLED)
::wallet.remove-account (enabled-in-env? :FLAG_REMOVE_ACCOUNT_ENABLED)
::wallet.network-filter (enabled-in-env? :FLAG_NETWORK_FILTER_ENABLED) ::wallet.network-filter (enabled-in-env? :FLAG_NETWORK_FILTER_ENABLED)
::profile.new-contact-ui (enabled-in-env? :FLAG_NEW_CONTACT_UI_ENABLED)})) ::profile.new-contact-ui (enabled-in-env? :FLAG_NEW_CONTACT_UI_ENABLED)
::community.edit-account-selection (enabled-in-env? :FLAG_EDIT_ACCOUNT_SELECTION_ENABLED)}))
(defn feature-flags [] @feature-flags-config) (defn feature-flags [] @feature-flags-config)

View File

@ -13,6 +13,8 @@
[status-im.contexts.chat.messenger.messages.view :as chat] [status-im.contexts.chat.messenger.messages.view :as chat]
[status-im.contexts.chat.messenger.photo-selector.view :as photo-selector] [status-im.contexts.chat.messenger.photo-selector.view :as photo-selector]
[status-im.contexts.communities.actions.accounts-selection.view :as communities.accounts-selection] [status-im.contexts.communities.actions.accounts-selection.view :as communities.accounts-selection]
[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.request-to-join.view :as join-menu] [status-im.contexts.communities.actions.request-to-join.view :as join-menu]
[status-im.contexts.communities.actions.share-community-channel.view :as share-community-channel] [status-im.contexts.communities.actions.share-community-channel.view :as share-community-channel]
[status-im.contexts.communities.discover.view :as communities.discover] [status-im.contexts.communities.discover.view :as communities.discover]
@ -122,9 +124,23 @@
:options options/transparent-screen-options :options options/transparent-screen-options
:component share-community-channel/view} :component share-community-channel/view}
{:name :community-account-selection ;; Note: the sheet screen is used when selecting addresses to share when
;; joining a community. The non-sheet screen is used when editing shared
;; addresses after the join request was sent.
{:name :community-account-selection-sheet
:options {:sheet? true} :options {:sheet? true}
:component communities.accounts-selection/view} :component communities.accounts-selection/view}
{:name :community-account-selection
:options {:insets {:top? true}}
:component communities.accounts-selection/view}
{:name :addresses-for-permissions
:options {:insets {:top? true}}
:component addresses-for-permissions/view}
{:name :address-for-airdrop
:options {:insets {:top? true}}
:component airdrop-addresses/view}
{:name :lightbox {:name :lightbox
:options options/lightbox :options options/lightbox

View File

@ -350,57 +350,6 @@
(fn [communities [_ community-id]] (fn [communities [_ community-id]]
(get-in communities [community-id :intro-message]))) (get-in communities [community-id :intro-message])))
(re-frame/reg-sub :communities/permissioned-balances-by-address
:<- [:communities/permissioned-balances]
(fn [balances [_ community-id account-address]]
(get-in balances [community-id (keyword account-address)])))
(re-frame/reg-sub
:communities/selected-permission-addresses
(fn [[_ community-id]]
[(re-frame/subscribe [:communities/community community-id])])
(fn [[{:keys [selected-permission-addresses]}] _]
selected-permission-addresses))
(re-frame/reg-sub
:communities/share-all-addresses?
(fn [[_ community-id]]
[(re-frame/subscribe [:communities/community community-id])])
(fn [[{:keys [share-all-addresses?]}] _]
share-all-addresses?))
(re-frame/reg-sub
:communities/unsaved-address-changes?
(fn [[_ community-id]]
[(re-frame/subscribe [:communities/community community-id])])
(fn [[{:keys [share-all-addresses? previous-share-all-addresses?
selected-permission-addresses previous-permission-addresses]}] _]
(or (not= share-all-addresses? previous-share-all-addresses?)
(not= selected-permission-addresses previous-permission-addresses))))
(re-frame/reg-sub
:communities/selected-permission-accounts
(fn [[_ community-id]]
[(re-frame/subscribe [:wallet/accounts-without-watched-accounts])
(re-frame/subscribe [:communities/selected-permission-addresses community-id])])
(fn [[accounts selected-permission-addresses]]
(filter #(contains? selected-permission-addresses (:address %)) accounts)))
(re-frame/reg-sub
:communities/airdrop-address
(fn [[_ community-id]]
[(re-frame/subscribe [:communities/community community-id])])
(fn [[{:keys [airdrop-address]}] _]
airdrop-address))
(re-frame/reg-sub
:communities/airdrop-account
(fn [[_ community-id]]
[(re-frame/subscribe [:wallet/accounts-with-customization-color])
(re-frame/subscribe [:communities/airdrop-address community-id])])
(fn [[accounts airdrop-address]]
(first (filter #(= (:address %) airdrop-address) accounts))))
(re-frame/reg-sub (re-frame/reg-sub
:communities/token-images-by-symbol :communities/token-images-by-symbol
(fn [[_ community-id]] (fn [[_ community-id]]

View File

@ -358,48 +358,6 @@
: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
[sub-name]
(testing "returns airdrop account"
(swap! rf-db/app-db assoc-in [:communities community-id :airdrop-address] "0x1")
(swap! rf-db/app-db assoc
:wallet
{:accounts {"0x1" {:address "0x1"
:color :blue
:name "account1"}
"0x2" {:address "0x2"
:color :orange
:name "account2"}}})
(is (match? {:address "0x1"
:network-preferences-names #{}
:name "account1"
:color :blue
:customization-color :blue}
(rf/sub [sub-name community-id])))))
(h/deftest-sub :communities/selected-permission-accounts
[sub-name]
(testing "returns selected permission accounts"
(swap! rf-db/app-db assoc-in
[:communities community-id :selected-permission-addresses]
#{"0x1" "0x3"})
(swap! rf-db/app-db assoc
:wallet
{:accounts {"0x1" {:address "0x1" :color :blue :name "account1"}
"0x2" {:address "0x2" :color :orange :name "account2"}
"0x3" {:address "0x3" :color :purple :name "account3"}}})
(is (match? [{:address "0x1"
:color :blue
:customization-color :blue
:network-preferences-names #{}
:name "account1"}
{:address "0x3"
:color :purple
:customization-color :purple
:network-preferences-names #{}
:name "account3"}]
(rf/sub [sub-name community-id])))))
(h/deftest-sub :communities/community-color (h/deftest-sub :communities/community-color
[sub-name] [sub-name]
(testing "returns the community color" (testing "returns the community color"

View File

@ -0,0 +1,69 @@
(ns status-im.subs.community.account-selection
(:require
[re-frame.core :as re-frame]))
;; This sub is particularly useful in context tags in page-nav or drawer-top
;; components. It should be preferred instead of subscribing to the whole
;; community.
(re-frame/reg-sub :communities/for-context-tag
(fn [[_ community-id]]
[(re-frame/subscribe [:communities/community community-id])])
(fn [[{:keys [name images color]}]]
{:name name
:logo (get-in images [:thumbnail :uri])
:color color}))
(re-frame/reg-sub :communities/addresses-to-reveal
:<- [:communities/all-addresses-to-reveal]
(fn [addresses-by-id [_ community-id]]
(get addresses-by-id community-id)))
(re-frame/reg-sub :communities/airdrop-address
:<- [:communities/all-airdrop-addresses]
(fn [addresses-by-id [_ community-id]]
(get addresses-by-id community-id)))
(re-frame/reg-sub :communities/share-all-addresses?
:<- [:communities/selected-share-all-addresses]
(fn [flags-by-id [_ community-id]]
(get flags-by-id community-id)))
(re-frame/reg-sub :communities/can-edit-shared-addresses?
(fn [[_ community-id]]
[(re-frame/subscribe [:communities/community community-id])
(re-frame/subscribe [:communities/my-pending-request-to-join community-id])])
(fn [[{:keys [joined]} pending-requests]]
(boolean (or joined (seq pending-requests)))))
(re-frame/reg-sub :communities/permissions-check-for-selection
:<- [:communities/permissions-checks-for-selection]
(fn [permissions [_ id]]
(get permissions id)))
(re-frame/reg-sub :communities/highest-role-for-selection
(fn [[_ community-id]]
[(re-frame/subscribe [:communities/permissions-check-for-selection community-id])])
(fn [[permissions-check] _]
(get-in permissions-check [:check :highestRole :type])))
(re-frame/reg-sub :communities/accounts-to-reveal
(fn [[_ community-id]]
[(re-frame/subscribe [:wallet/accounts-without-watched-accounts])
(re-frame/subscribe [:communities/addresses-to-reveal community-id])])
(fn [[accounts addresses] _]
(filter #(contains? addresses (:address %))
accounts)))
(re-frame/reg-sub :communities/airdrop-account
(fn [[_ community-id]]
[(re-frame/subscribe [:wallet/accounts-without-watched-accounts])
(re-frame/subscribe [:communities/airdrop-address community-id])])
(fn [[accounts airdrop-address] _]
(->> accounts
(filter #(= (:address %) airdrop-address))
first)))
(re-frame/reg-sub :communities/permissioned-balances-by-address
:<- [:communities/permissioned-balances]
(fn [balances [_ community-id account-address]]
(get-in balances [community-id (keyword account-address)])))

View File

@ -0,0 +1,75 @@
(ns status-im.subs.community.account-selection-test
(:require
[cljs.test :refer [is testing]]
matcher-combinators.test
[re-frame.db :as rf-db]
status-im.subs.communities
[test-helpers.unit :as h]
[utils.re-frame :as rf]))
(def community-id "0x1")
(h/deftest-sub :communities/for-context-tag
[sub-name]
(reset! rf-db/app-db
{:communities
{community-id {:name "foo"
:images {:thumbnail {:uri "data:image/png;"}}
:color :orange}}})
(is (match? {:name "foo"
:logo "data:image/png;"
:color :orange}
(rf/sub [sub-name community-id]))))
(h/deftest-sub :communities/can-edit-shared-addresses?
[sub-name]
(testing "joined community and there are pending requests"
(reset! rf-db/app-db
{:communities {community-id {:id community-id :joined true}}
:communities/my-pending-request-to-join {community-id {:id :request-id-1}}})
(is (true? (rf/sub [sub-name community-id]))))
(testing "joined community and there are no pending requests"
(reset! rf-db/app-db
{:communities {community-id {:id community-id :joined true}}
:communities/my-pending-request-to-join {community-id {}}})
(is (true? (rf/sub [sub-name community-id]))))
(testing "not joined community and there are pending requests"
(reset! rf-db/app-db
{:communities {community-id {:id community-id :joined false}}
:communities/my-pending-request-to-join {community-id {:id :request-id-1}}})
(is (false? (rf/sub [sub-name community-id]))))
(testing "not joined community and there are no pending requests"
(reset! rf-db/app-db
{:communities {community-id {:id community-id :joined false}}
:communities/my-pending-request-to-join {community-id {}}})
(is (false? (rf/sub [sub-name community-id])))))
(h/deftest-sub :communities/airdrop-account
[sub-name]
(let [airdrop-account {:address "0xA" :position 1}]
(reset! rf-db/app-db
{:communities/all-airdrop-addresses {community-id "0xA"}
:wallet {:accounts {"0xB" {:address "0xB" :position 0}
"0xA" airdrop-account}}})
(is (match? airdrop-account (rf/sub [sub-name community-id])))))
(h/deftest-sub :communities/accounts-to-reveal
[sub-name]
(reset! rf-db/app-db
{:communities/all-addresses-to-reveal {community-id #{"0xC" "0xB"}}
:wallet {:accounts {"0xB" {:address "0xB" :position 0}
"0xA" {:address "0xA" :position 1}
"0xC" {:address "0xC" :position 2}}}})
(is (match? [{:address "0xB" :position 0}
{:address "0xC" :position 2}]
(rf/sub [sub-name community-id]))))

View File

@ -6,6 +6,7 @@
status-im.subs.biometrics status-im.subs.biometrics
status-im.subs.chats status-im.subs.chats
status-im.subs.communities status-im.subs.communities
status-im.subs.community.account-selection
status-im.subs.contact status-im.subs.contact
status-im.subs.general status-im.subs.general
status-im.subs.messages status-im.subs.messages
@ -150,6 +151,11 @@
(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/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)
(reg-root-key-sub :communities/permissions-checks-for-selection
:communities/permissions-checks-for-selection)
(reg-root-key-sub :communities/channel-permissions-check :communities/channel-permissions-check) (reg-root-key-sub :communities/channel-permissions-check :communities/channel-permissions-check)
;;activity center ;;activity center

View File

@ -2279,6 +2279,19 @@
"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",
"confirm-and-leave": "Confirm and leave",
"membership-requirements-not-met": "Membership requirements not met",
"edit-shared-addresses": "Edit shared addresses",
"leave-community-farewell": "Well be sad to see you go but remember, you can come back at any time! All shared addresses will be unshared.",
"pending-join-request-farewell": "Well be sad to see you go but remember, you can request to join this community at any point. All shared addresses will be unshared.",
"all-changes-will-be-discarded": "All changes in shared addresses for permissions will be discarded.",
"cancel-request": "Cancel request",
"discard": "Discard",
"discard-changes?": "Discard changes?",
"confirm-and-cancel": "Confirm and cancel",
"you-will-be-a": "Youll be a",
"you-are-a": "Youre a",
"you-are-a-role": "Youre a {{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-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",