feat: add ability to cancel a request to join a community (#14973)

80d350ad...5d818669
This commit is contained in:
Jamie Caprani 2023-03-14 23:14:37 +00:00 committed by GitHub
parent 61600a534b
commit de6a736c10
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 256 additions and 88 deletions

View File

@ -4,6 +4,7 @@
[clojure.walk :as walk]
[quo.design-system.colors :as colors]
[re-frame.core :as re-frame]
[status-im.utils.types :as types]
[status-im.async-storage.core :as async-storage]
[status-im.ui.components.emoji-thumbnail.styles :as emoji-thumbnail-styles]
[status-im.utils.universal-links.core :as universal-links]
@ -36,9 +37,9 @@
:chatId :chat-id}))
(defn <-requests-to-join-community-rpc
[requests]
[requests key-fn]
(reduce (fn [acc r]
(assoc acc (:id r) (<-request-to-join-community-rpc r)))
(assoc acc (key-fn r) (<-request-to-join-community-rpc r)))
{}
requests))
@ -80,14 +81,28 @@
(update :chats <-chats-rpc)
(update :categories <-categories-rpc)))
(defn fetch-community-id-input
(defn- fetch-community-id-input
[{:keys [db]}]
(:communities/community-id-input db))
(defn- handle-my-request
[db {:keys [community-id state] :as request}]
{:db (if (= constants/community-request-to-join-state-pending state)
(assoc-in db [:communities/my-pending-requests-to-join community-id] request)
(update-in db [:communities/my-pending-requests-to-join] dissoc community-id))})
(defn handle-admin-request
[db {:keys [id community-id] :as request}]
{:db (assoc-in db [:communities/requests-to-join community-id id] request)})
(rf/defn handle-request-to-join
[{:keys [db]} r]
(let [{:keys [id community-id] :as request} (<-request-to-join-community-rpc r)]
{:db (assoc-in db [:communities/requests-to-join community-id id] request)}))
(let [my-public-key (get-in db [:multiaccount :public-key])
{:keys [id community-id public-key] :as request} (<-request-to-join-community-rpc r)
my-request? (= my-public-key public-key)]
(if my-request?
(handle-my-request db request)
(handle-admin-request db request))))
(rf/defn handle-removed-chats
[{:keys [db]} chat-ids]
@ -109,6 +124,14 @@
db
communities)})
(rf/defn handle-my-pending-requests-to-join
{:events [:communities/fetched-my-communities-requests-to-join]}
[{:keys [db]} my-requests]
{:db (assoc db
:communities/my-pending-requests-to-join
(<-requests-to-join-community-rpc (types/js->clj my-requests)
:communityId))})
(rf/defn handle-response
[_ response-js]
{:dispatch [:sanitize-messages-and-process-response response-js]})
@ -130,6 +153,7 @@
[cofx response-js]
(let [[event-name _] (:event cofx)
community-name (aget response-js "communities" 0 "name")]
(js/console.log "event-name")
(rf/merge cofx
(handle-response cofx response-js)
(toasts/upsert {:icon :correct
@ -139,9 +163,31 @@
:t/requested-to-join-community)
{:community community-name})}))))
(rf/defn requested-to-join
{:events [:communities/requested-to-join]}
[cofx response-js]
(let [community-name (aget response-js "communities" 0 "name")]
(rf/merge cofx
(handle-response cofx response-js)
(toasts/upsert {:icon :correct
:icon-color (:positive-01 @colors/theme)
:text (i18n/label
:t/requested-to-join-community
{:community community-name})}))))
(rf/defn cancelled-requested-to-join
{:events [:communities/cancelled-request-to-join]}
[cofx response-js]
(rf/merge cofx
(handle-response cofx response-js)
(toasts/upsert {:icon :correct
:icon-color (:positive-01 @colors/theme)
:text (i18n/label
:t/you-canceled-the-request)})))
(rf/defn export
{:events [::export-pressed]}
[cofx community-id]
[_ community-id]
{:json-rpc/call [{:method "wakuext_exportCommunity"
:params [community-id]
:on-success #(re-frame/dispatch [:show-popover
@ -175,14 +221,35 @@
(rf/defn request-to-join
{:events [:communities/request-to-join]}
[cofx community-id]
[_ community-id]
{:json-rpc/call [{:method "wakuext_requestToJoinCommunity"
:params [{:communityId community-id}]
:js-response true
:on-success #(re-frame/dispatch [::requested-to-join %])
:on-error #(do
(log/error "failed to request to join community" community-id %)
(re-frame/dispatch [::failed-to-request-to-join %]))}]})
:on-success #(re-frame/dispatch [:communities/requested-to-join %])
:on-error (fn []
(log/error "failed to request to join community" community-id)
(re-frame/dispatch [::failed-to-request-to-join]))}]})
(rf/defn get-user-requests-to-join
{:events [:communities/get-user-requests-to-join]}
[_]
{:json-rpc/call [{:method "wakuext_myPendingRequestsToJoin"
:params []
:js-response true
:on-success #(re-frame/dispatch
[:communities/fetched-my-communities-requests-to-join %])
:on-error #(log/error "failed to get requests to join community")}]})
(rf/defn cancel-request-to-join
{:events [:communities/cancel-request-to-join]}
[_ request-to-join-id]
{:json-rpc/call [{:method "wakuext_cancelRequestToJoinCommunity"
:params [{:id request-to-join-id}]
:on-success #(re-frame/dispatch [:communities/cancelled-request-to-join %])
:js-response true
:on-error #(log/error "failed to cancel request to join community"
request-to-join-id
%)}]})
(rf/defn leave
{:events [:communities/leave]}
@ -196,11 +263,10 @@
:params [community-id]
:js-response true
:on-success #(re-frame/dispatch [::left %])
:on-error #(do
(log/error "failed to leave community"
community-id
%)
(re-frame/dispatch [::failed-to-leave %]))}]}))
:on-error (fn []
(log/error "failed to leave community"
community-id)
(re-frame/dispatch [::failed-to-leave]))}]}))
(rf/defn status-tag-pressed
{:events [:communities/status-tag-pressed]}
@ -555,7 +621,7 @@
[{:keys [db]} community-id requests]
{:db (assoc-in db
[:communities/requests-to-join community-id]
(<-requests-to-join-community-rpc requests))})
(<-requests-to-join-community-rpc requests :id))})
(rf/defn fetch-requests-to-join
{:events [::fetch-requests-to-join]}

View File

@ -405,6 +405,7 @@
(data-store.chats/fetch-chats-rpc
{:on-success
#(do (re-frame/dispatch [:chats-list/load-success %])
(rf/dispatch [:communities/get-user-requests-to-join])
(re-frame/dispatch [::get-chats-callback]))})
(initialize-appearance)
(initialize-communities-enabled)

View File

@ -121,6 +121,7 @@
(def ^:const status-create-address "status_createaddress")
(def ^:const community-unknown-membership-access 0)
(def ^:const community-no-membership-access 1)
(def ^:const community-invitation-only-access 2)
(def ^:const community-on-request-access 3)
@ -132,7 +133,12 @@
(def ^:const community-channel-access-invitation-only 2)
(def ^:const community-channel-access-on-request 3)
; BIP44 Wallet Root Key, the extended key from which any wallet can be derived
(def ^:const community-request-to-join-state-pending 1)
(def ^:const community-request-to-join-state-declined 2)
(def ^:const community-request-to-join-state-accepted 3)
(def ^:const community-request-to-join-state-cancelled 4)
; BIP44 Wallet Root Key, the extended key from which any wallet can be derived
(def ^:const path-wallet-root "m/44'/60'/0'/0")
; EIP1581 Root Key, the extended key from which any whisper key/encryption key can be derived
(def ^:const path-eip1581 "m/43'/60'/1581'")

View File

@ -2,24 +2,19 @@
(ns status-im2.contexts.communities.menus.community-options.component-spec
(:require [re-frame.core :as re-frame]
[test-helpers.component :as h]
[utils.i18n :as i18n]
[status-im2.setup.i18n-resources :as i18n-resources]
[status-im2.contexts.communities.menus.community-options.view :as options]))
(defn init
[]
(i18n/set-language "en")
(i18n-resources/load-language "en"))
(defn setup-sub
[opts]
(re-frame/reg-sub
:communities/community
(fn [_] opts)))
(defn setup-subs
[subs]
(doseq [keyval subs]
(re-frame/reg-sub
(key keyval)
(fn [_] (val keyval)))))
(h/describe "community options for bottom sheets"
(h/test "joined options - Non token Gated"
(setup-sub {:joined true})
(setup-subs {:communities/my-pending-request-to-join nil
:communities/community {:joined true}})
(h/render [options/community-options-bottom-sheet {:id "test"}])
(-> (h/expect (h/get-by-translation-text :view-members))
(.toBeTruthy))
@ -41,8 +36,9 @@
(.toBeTruthy)))
(h/test "joined options - Token Gated"
(setup-sub {:joined true
:token-gated? true})
(setup-subs {:communities/my-pending-request-to-join nil
:communities/community {:joined true
:token-gated? true}})
(h/render [options/community-options-bottom-sheet {:id "test"}])
(-> (h/expect (h/get-by-translation-text :view-members))
(.toBeTruthy))
@ -66,7 +62,8 @@
(.toBeTruthy)))
(h/test "admin options - Non token Gated"
(setup-sub {:admin true})
(setup-subs {:communities/my-pending-request-to-join nil
:communities/community {:admin true}})
(h/render [options/community-options-bottom-sheet {:id "test"}])
(-> (h/expect (h/get-by-translation-text :view-members))
(.toBeTruthy))
@ -88,8 +85,9 @@
(.toBeTruthy)))
(h/test "admin options - Token Gated"
(setup-sub {:admin true
:token-gated? true})
(setup-subs {:communities/my-pending-request-to-join nil
:communities/community {:admin true
:token-gated? true}})
(h/render [options/community-options-bottom-sheet {:id "test"}])
(-> (h/expect (h/get-by-translation-text :view-members))
(.toBeTruthy))
@ -111,7 +109,8 @@
(.toBeTruthy)))
(h/test "request sent options - Non token Gated"
(setup-sub {:requested-to-join-at true})
(setup-subs {:communities/my-pending-request-to-join "mock-id"
:communities/community {}})
(h/render [options/community-options-bottom-sheet {:id "test"}])
(-> (h/expect (h/get-by-translation-text :view-members))
(.toBeTruthy))
@ -127,8 +126,8 @@
(.toBeTruthy)))
(h/test "request sent options - Token Gated"
(setup-sub {:requested-to-join-at 100
:token-gated? true})
(setup-subs {:communities/my-pending-request-to-join "mock-id"
:communities/community {:token-gated? true}})
(h/render [options/community-options-bottom-sheet {:id "test"}])
(-> (h/expect (h/get-by-translation-text :invite-people-from-contacts))
(.toBeTruthy))
@ -142,7 +141,8 @@
(.toBeTruthy)))
(h/test "banned options - Non token Gated"
(setup-sub {:banList true})
(setup-subs {:communities/my-pending-request-to-join nil
:communities/community {:banList true}})
(h/render [options/community-options-bottom-sheet {:id "test"}])
(-> (h/expect (h/get-by-translation-text :view-members))
(.toBeTruthy))
@ -156,8 +156,9 @@
(.toBeTruthy)))
(h/test "banned options - Token Gated"
(setup-sub {:banList 100
:token-gated? true})
(setup-subs {:communities/my-pending-request-to-join nil
:communities/community {:banList 100
:token-gated? true}})
(h/render [options/community-options-bottom-sheet {:id "test"}])
(-> (h/expect (h/get-by-translation-text :invite-people-from-contacts))
(.toBeTruthy))
@ -169,8 +170,9 @@
(.toBeTruthy)))
(h/test "banned options - Token Gated"
(setup-sub {:banList 100
:token-gated? true})
(setup-subs {:communities/my-pending-request-to-join nil
:communities/community {:banList 100
:token-gated? true}})
(h/render [options/community-options-bottom-sheet {:id "test"}])
(-> (h/expect (h/get-by-translation-text :invite-people-from-contacts))
(.toBeTruthy))
@ -182,9 +184,10 @@
(.toBeTruthy)))
(h/test "joined and muted community"
(setup-sub {:joined true
:muted true
:token-gated? true})
(setup-subs {:communities/my-pending-request-to-join nil
:communities/community {:joined true
:muted true
:token-gated? true}})
(h/render [options/community-options-bottom-sheet {:id "test"}])
(-> (h/expect (h/get-by-translation-text :unmute-community))
(.toBeTruthy))))

View File

@ -85,19 +85,21 @@
{:icon :i/log-out
:label (i18n/label :t/leave-community)
:accessibility-label :leave-community
:danger? true
:on-press #(rf/dispatch [:bottom-sheet/show-sheet
{:content (constantly [leave-menu/leave-sheet id])
:content-height 400}])})
(defn cancel-request-to-join
[id]
[id request-id]
{:icon :i/block
:label (i18n/label :t/cancel-request-to-join)
:accessibility-label :cancel-request-to-join
:danger? true
:on-press #(js/alert (str "implement action" id))})
:on-press #(rf/dispatch [:bottom-sheet/show-sheet
{:content (constantly [leave-menu/cancel-request-sheet id
request-id])
:content-height 400}])})
(defn edit-community
[id]
@ -117,9 +119,9 @@
(share-community id)]])
(defn join-request-sent-options
[id token-gated?]
[id token-gated? request-id]
[(conj (first (not-joined-options id token-gated?))
(assoc (cancel-request-to-join id) :add-divider? true))])
(assoc (cancel-request-to-join id request-id) :add-divider? true))])
(defn banned-options
[id token-gated?]
@ -153,15 +155,15 @@
(defn get-context-drawers
[{:keys [id]}]
(let [{:keys [token-gated? admin joined requested-to-join-at
(let [{:keys [token-gated? admin joined
muted banList]} (rf/sub [:communities/community id])
request-sent? (pos? requested-to-join-at)]
request-id (rf/sub [:communities/my-pending-request-to-join id])]
(cond
admin (owner-options id token-gated? muted)
joined (joined-options id token-gated? muted)
request-sent? (join-request-sent-options id token-gated?)
banList (banned-options id token-gated?)
:else (not-joined-options id token-gated?))))
admin (owner-options id token-gated? muted)
joined (joined-options id token-gated? muted)
request-id (join-request-sent-options id token-gated? request-id)
banList (banned-options id token-gated?)
:else (not-joined-options id token-gated?))))
(defn community-options-bottom-sheet
[id]

View File

@ -1,12 +1,11 @@
(ns status-im2.contexts.communities.menus.generic-menu.view
(:require [utils.i18n :as i18n]
[quo2.core :as quo]
(:require [quo2.core :as quo]
[status-im2.contexts.communities.menus.generic-menu.style :as style]
[react-native.core :as rn]
[utils.re-frame :as rf]))
(defn view
[id children]
[{:keys [id title]} children]
(let [{:keys [name images]} (rf/sub [:communities/community id])]
[rn/view {:style style/container}
[rn/view {:style style/inner-container}
@ -14,7 +13,7 @@
{:accessibility-label :communities-join-community
:weight :semi-bold
:size :heading-1}
(i18n/label :t/leave-community?)]]
title]]
[quo/context-tag
{:style style/context-tag}
(:thumbnail images) name]

View File

@ -14,7 +14,8 @@
(defn leave-sheet
[id]
[generic-menu/view
id
{:id id
:title (i18n/label :t/leave-community?)}
[:<>
[quo/text
{:accessibility-label :communities-join-community
@ -32,3 +33,28 @@
{:on-press #(hide-sheet-and-dispatch [:communities/leave id])
:style style/action-button}
(i18n/label :t/leave-community)]]]])
(defn cancel-request-sheet
[id request-id]
[generic-menu/view
{:id id
:title (i18n/label :t/cancel-request?)}
[:<>
[quo/text
{:accessibility-label :communities-join-community
:size :paragraph-1
:style style/text}
(i18n/label :t/if-you-cancel)]
[rn/view
{:style style/button-container}
[quo/button
{:accessibility-label :cancel-button
:on-press #(rf/dispatch [:bottom-sheet/hide])
:type :grey
:style style/cancel-button}
(i18n/label :t/close)]
[quo/button
{:accessibility-label :confirm-button
:on-press #(hide-sheet-and-dispatch [:communities/cancel-request-to-join request-id])
:style style/action-button}
(i18n/label :t/confirm)]]]])

View File

@ -23,7 +23,9 @@
can-join?
can-request-access?
requested-to-join-at]}]
(let [agreed-to-rules? (reagent/atom false)]
(let [agreed-to-rules? (reagent/atom false)
pending? (rf/sub [:communities/my-pending-request-to-join id])
]
[safe-area/consumer
(fn [insets]
[:f>
@ -73,7 +75,7 @@
(rf/dispatch [:communities/join id])
(rf/dispatch [:bottom-sheet/hide]))
(do (and can-request-access?
(not (pos? requested-to-join-at))
(not pending?)
(requests/can-request-access-again?
requested-to-join-at))
(rf/dispatch [:communities/request-to-join id])

View File

@ -1,9 +1,12 @@
(ns status-im2.contexts.communities.menus.see-rules.view
(:require [status-im2.contexts.communities.menus.generic-menu.view :as generic-menu]
[status-im2.contexts.communities.menus.community-rules-list.view :as community-rules]))
[status-im2.contexts.communities.menus.community-rules-list.view :as community-rules]
[utils.i18n :as i18n]))
(defn view
[id]
[generic-menu/view
id
{:id id
:title (i18n/label :t/community-rules)
}
[community-rules/view community-rules/rules]])

View File

@ -107,15 +107,25 @@
(i18n/label :t/join-open-community)
(i18n/label :t/request-to-join-community)))
(defn get-access-type
[access]
(case access
constants/community-no-membership-access :open
constants/community-invitation-only-access :invite-only
constants/community-on-request-access :request-access
:unknown-access))
(defn join-community
[{:keys [joined can-join? requested-to-join-at
[{:keys [joined can-join?
community-color permissions]
:as community}]
(let [pending? (pos? requested-to-join-at)
is-open? (not= constants/community-channel-access-on-request (:access permissions))
node-offline? (and can-join? (not joined) (pos? requested-to-join-at))]
:as community} pending?]
(let [access-type (get-access-type (:access permissions))
unknown-access? (= access-type :unknown-access)
invite-only? (= access-type :invite-only)
is-open? (= access-type :open)
node-offline? (and can-join? (not joined) pending?)]
[:<>
(when-not (or joined pending?)
(when-not (or joined pending? invite-only? unknown-access?)
[quo/button
{:on-press #(rf/dispatch
[:bottom-sheet/show-sheet
@ -210,12 +220,11 @@
(defn community-content
[{:keys [name description locked joined images
status tokens tags requested-to-join-at id]
:as community}
status tokens tags id]
:as community} pending?
{:keys [on-categories-heights-changed
on-first-channel-height-changed]}]
(let [pending? (pos? requested-to-join-at)
thumbnail-image (get-in images [:thumbnail])
(let [thumbnail-image (:thumbnail images)
chats-by-category (rf/sub [:communities/categorized-channels id])
users (rf/sub [:communities/users id])]
[rn/view
@ -253,7 +262,7 @@
[rn/view {:margin-top 12}]
[quo/community-tags tags]
[preview-user-list users]
[join-community community]]
[join-community community pending?]]
[channel-list-component
{:on-categories-heights-changed #(on-categories-heights-changed %)
:on-first-channel-height-changed #(on-first-channel-height-changed %)}
@ -290,7 +299,7 @@
scroll-height (reagent/atom 0)
cover {:uri (get-in images [:banner :uri])}
logo {:uri (get-in images [:thumbnail :uri])}]
(fn [community]
(fn [community pending?]
[scroll-page/scroll-page
{:cover-image cover
:logo logo
@ -312,16 +321,18 @@
@categories-heights))}]
[community-content
community
pending?
{:on-categories-heights-changed #(reset! categories-heights %)
:on-first-channel-height-changed #(reset! first-channel-height %)}]])))
(defn overview
[]
(let [id (rf/sub [:get-screen-params :community-overview])
community (rf/sub [:communities/community id])]
community (rf/sub [:communities/community id])
pending? (rf/sub [:communities/my-pending-request-to-join id])]
[rn/view
{:style style/community-overview-container}
[community-card-page-view community]
[community-card-page-view community pending?]
[floating-shell-button/floating-shell-button
{:jump-to {:on-press #(rf/dispatch [:shell/navigate-to-jump-to])
:label (i18n/label :t/jump-to)}}

View File

@ -123,14 +123,20 @@
(re-frame/reg-sub
:communities/grouped-by-status
:<- [:communities/communities]
(fn [communities]
:<- [:communities/my-pending-requests-to-join]
;; Return communities splitted by level of user participation. Some communities user
;; already joined, to some of them join request sent and others were opened one day
;; and their data remained in app-db.
;; Result map has form: {:joined [id1, id2] :pending [id3, id5] :opened [id4]}"
(fn [[communities requests]]
(reduce (fn [acc community]
(let [joined? (:joined community)
requested? (pos? (:requested-to-join-at community))] ;; this looks suspicious
(let [joined? (:joined community)
community-id (:id community)
pending? (boolean (get requests community-id))]
(cond
joined? (update acc :joined conj community)
requested? (update acc :pending conj community)
:else (update acc :opened conj community))))
joined? (update acc :joined conj community)
pending? (update acc :pending conj community)
:else (update acc :opened conj community))))
{:joined [] :pending [] :opened []}
communities)))
@ -152,6 +158,12 @@
(get communities identity)
counts)))
(re-frame/reg-sub
:communities/my-pending-request-to-join
:<- [:communities/my-pending-requests-to-join]
(fn [requests [_ community-id]]
(:id (get requests community-id))))
(re-frame/reg-sub
:communities/edited-community
:<- [:communities]

View File

@ -156,3 +156,36 @@
[{:name "chat1" :emoji nil :locked? false :id "0x1" :unread-messages? true :mentions-count 2}
{:name "chat2" :emoji nil :locked? true :id "0x2" :unread-messages? false :mentions-count 0}]}
(rf/sub [sub-name "0x1"])))))
(h/deftest-sub :communities/my-pending-requests-to-join
[sub-name]
(testing "no requests"
(swap! rf-db/app-db assoc
:communities/my-pending-requests-to-join
{})
(is (= {}
(rf/sub [sub-name]))))
(testing "users requests to join different communities"
(swap! rf-db/app-db assoc
:communities/my-pending-requests-to-join
{:community-id-1 {:id :request-id-1}
:community-id-2 {:id :request-id-2}})
(is (= {:community-id-1 {:id :request-id-1}
:community-id-2 {:id :request-id-2}}
(rf/sub [sub-name])))))
(h/deftest-sub :communities/my-pending-request-to-join
[sub-name]
(testing "no request for community"
(swap! rf-db/app-db assoc
:communities/my-pending-requests-to-join
{})
(is (= nil
(rf/sub [sub-name :community-id-1]))))
(testing "users request to join a specific communities"
(swap! rf-db/app-db assoc
:communities/my-pending-requests-to-join
{:community-id-1 {:id :request-id-1}
:community-id-2 {:id :request-id-2}})
(is (= :request-id-1
(rf/sub [sub-name :community-id-1])))))

View File

@ -245,6 +245,7 @@
(reg-root-key-sub :communities/community-id-input :communities/community-id-input)
(reg-root-key-sub :communities/enabled? :communities/enabled?)
(reg-root-key-sub :communities/resolve-community-info :communities/resolve-community-info)
(reg-root-key-sub :communities/my-pending-requests-to-join :communities/my-pending-requests-to-join)
(reg-root-key-sub :activity-center :activity-center)

View File

@ -127,6 +127,7 @@
"can-not-add-yourself": "That's you, to start a chat choose someone else",
"cancel": "Cancel",
"cancel-keycard-setup": "Cancel Keycard setup",
"cancel-request?": "Cancel request?",
"cancel-request-to-join": "Cancel request to join",
"cannot-read-card": "Can't read card.\nPlease hold it to the back of your phone",
"cannot-use-default-pin": "Passcode 000000 is not allowed.\nPlease use another number",
@ -663,6 +664,7 @@
"home": "Home",
"hooks": "Hooks",
"identifier": "Identifier",
"if-you-cancel": "If you cancel, you can request to join this community at any point.",
"image-remove-current": "Remove current photo",
"image-source-gallery": "Select from gallery",
"image-source-make-photo": "Capture",
@ -1466,6 +1468,7 @@
"you-are-all-set": "Youre all set!",
"you-are-all-set-description": "If you lose your phone, you can now access your funds and chat key using your seed phrase",
"you-can-change-account": "You can change the account name and color to what you wish",
"you-canceled-the-request": "You canceled the request to join",
"you-dont-have-stickers": "You dont have any stickers yet",
"you-dont-have-contacts-invite-friends": "You dont have any contacts yet.\nInvite your friends to start chatting.",
"your-contact-code": "Granting access authorizes this DApp to retrieve your chat key",