Update UI for referrals flow

Cleanup http events

Add loaders

Add dapp flow

Fix tests

Fix dapp success event

Hide reward based on referrals metadata

New flows

Terms link

TX watcher event

Do not show success popup

Use new rewardable field from backend

Fix referral pulling

Update copy

Translate advertiser modal

Move modal styles into style ns

Signed-off-by: Gheorghe Pinzaru <feross95@gmail.com>
This commit is contained in:
Gheorghe Pinzaru 2020-08-04 09:44:10 +03:00
parent 25721688db
commit f17b401bbc
No known key found for this signature in database
GPG Key ID: C9A094959935A952
39 changed files with 698 additions and 466 deletions

View File

@ -24,6 +24,8 @@
:positive-02 "rgba(78,188,96,0.1)" ; Secondary Positive, Supporting color for success illustrations
:negative-01 "rgba(255,45,85,1)" ; Primary Negative, text, icons color
:negative-02 "rgba(255,45,85,0.1))" ; Secondary Negative, Supporting color for errors illustrations
:warning-01 "rgba(255, 202, 15, 1)"
:warning-02 "rgba(255, 202, 15, 0.1)"
:interactive-01 "rgba(67,96,223,1)" ; Accent color, buttons, own message, actions,active state
:interactive-02 "rgba(236,239,252,1)" ; Light Accent, buttons background, actions background, messages
:interactive-03 "rgba(255,255,255,0.1)" ; Background for interactive above accent
@ -51,6 +53,8 @@
:positive-02 "rgba(78,188,96,0.1)"
:negative-01 "rgba(252,95,95,1)"
:negative-02 "rgba(252,95,95,0.1)"
:warning-01 "rgba(255, 202, 15, 1)"
:warning-02 "rgba(255, 202, 15, 0.1)"
:interactive-01 "rgba(97,119,229,1)"
:interactive-02 "rgba(35,37,47,1)"
:interactive-03 "rgba(255,255,255,0.1)"

View File

@ -10,7 +10,7 @@
[cofx _]
(popover/show-popover cofx
{:prevent-closing? true
:view :accept-invite}))
:view :advertiser-invite}))
(fx/defn advertiser-decide
{:events [::decision]}
@ -24,6 +24,6 @@
(gateway/handle-acquisition {:message payload
:method "PATCH"
:url (gateway/get-url :clicks referral)
:on-success ::claim/success-starter-pack-claim})
:on-success [::claim/success-starter-pack-claim]})
{::persistence/set-referrer-state :declined})
(popover/hide-popover))))

View File

@ -23,7 +23,7 @@
(fx/defn accept-pack
{:events [::accept-pack]}
[{:keys [db] :as cofx} decision]
[{:keys [db] :as cofx}]
(let [referral (get-in db [:acquisition :referrer])
payload {:chat_key (get-in db [:multiaccount :public-key])
:address (ethereum/default-address db)
@ -33,4 +33,4 @@
(gateway/handle-acquisition {:message payload
:method "PATCH"
:url (gateway/get-url :clicks referral)
:on-success ::claim/success-starter-pack-claim}))))
:on-success [::claim/success-starter-pack-claim]}))))

View File

@ -13,6 +13,7 @@
:message (i18n/label :t/starter-pack-received-description)}})
(fx/defn add-tx-watcher
{:events [::add-tx-watcher]}
[cofx tx]
(transaction/watch-transaction cofx
tx
@ -26,6 +27,7 @@
[cofx {:keys [tx]}]
(fx/merge cofx
{::persistence/set-watch-tx tx
::persistence/set-referrer-state :accepted}
(add-tx-watcher tx)
::persistence/set-referrer-state (if tx :accepted :claimed)}
(when tx
(add-tx-watcher tx))
(notifications/request-permission)))

View File

@ -6,6 +6,8 @@
[status-im.ethereum.ens :as ens]
[status-im.ethereum.contracts :as contracts]
[status-im.acquisition.chat :as chat]
[status-im.acquisition.dapp :as dapp]
[status-im.acquisition.claim :as claim]
[status-im.acquisition.advertiser :as advertiser]
[status-im.acquisition.persistance :as persistence]
[status-im.acquisition.gateway :as gateway]
@ -14,6 +16,7 @@
(def not-found-code "notfound.click_id")
(def advertiser-type "advertiser")
(def chat-type "chat")
(def dapp-type "dapp")
(fx/defn handle-registration
[cofx {:keys [message on-success]}]
@ -59,7 +62,10 @@
(advertiser/start-acquisition referrer-meta)
(= type chat-type)
(chat/start-acquisition referrer-meta)))))
(chat/start-acquisition referrer-meta)
(= type dapp-type)
(dapp/start-acquisition referrer-meta)))))
(fx/defn outdated-referrer
{:events [::outdated-referrer]}
@ -80,13 +86,14 @@
referrer
(fn [resp] [::referrer-registered referrer resp])
(fn [{:keys [code]}] (= code not-found-code))
(fn [resp] [::outdated-referrer resp]))
(fn [resp]
(re-frame/dispatch [::outdated-referrer resp])))
(= flow-state (:accepted persistence/referrer-state))
(fn [_]
{::persistence/check-tx-state (fn [tx]
(when-not (nil? tx)
(re-frame/dispatch [::add-tx-watcher tx])))})))))
(re-frame/dispatch [::claim/add-tx-watcher tx])))})))))
(re-frame/reg-fx
::resolve-contract
@ -95,7 +102,7 @@
(when contract
(if (string/starts-with? contract "0x")
(on-success contract)
(ens/resolver register contract on-success))))))
(ens/get-addr register contract on-success))))))
(fx/defn create [{:keys [db]}]
{::resolve-contract {:chain (ethereum/chain-keyword db)
@ -108,3 +115,8 @@
:contract (contracts/get-address db :status/acquisition)
:on-success #(re-frame/dispatch [:set-in [:acquisition :contract] %])}
::check-referrer nil})
(re-frame/reg-sub
::metadata
(fn [db]
(get-in db [:acquisition :metadata])))

View File

@ -0,0 +1,40 @@
(ns status-im.acquisition.dapp
(:require [status-im.utils.fx :as fx]
[status-im.popover.core :as popover]
[status-im.ethereum.core :as ethereum]
[status-im.acquisition.gateway :as gateway]
[status-im.acquisition.claim :as claim]
[status-im.browser.core :as browser]
[status-im.utils.security :as security]
[status-im.acquisition.persistance :as persistence]))
(fx/defn start-acquisition
[cofx _]
(popover/show-popover cofx
{:prevent-closing? true
:view :dapp-invite}))
(fx/defn succes-claim
{:events [::success-claim]}
[cofx response]
(let [link (get-in cofx [:db :acquisition :metadata :url])]
(fx/merge cofx
(when (security/safe-link? link)
(browser/open-url link))
(claim/success-starter-pack-claim response))))
(fx/defn dapp-decision
{:events [::decision]}
[{:keys [db] :as cofx} decision]
(let [referral (get-in db [:acquisition :referrer])
payload {:chat_key (get-in db [:multiaccount :public-key])
:address (ethereum/default-address db)
:invite_code referral}]
(fx/merge cofx
(if (= decision :accept)
(gateway/handle-acquisition {:message payload
:method "PATCH"
:url (gateway/get-url :clicks referral)
:on-success [::success-claim]})
{::persistence/set-referrer-state :declined})
(popover/hide-popover))))

View File

@ -1,6 +1,7 @@
(ns status-im.acquisition.gateway
(:require [re-frame.core :as re-frame]
[status-im.utils.fx :as fx]
[status-im.i18n :as i18n]
[status-im.ethereum.json-rpc :as json-rpc]
[status-im.waku.core :as waku]
[status-im.utils.types :as types]))
@ -10,6 +11,11 @@
(def acquisition-routes {:clicks (str acquisition-gateway "/clicks")
:registrations (str acquisition-gateway "/registrations")})
(def network-statuses {:initiated 1
:in-flight 2
:error 3
:success 4})
(defn get-url [type referral]
(if (= type :clicks)
(str (get acquisition-routes :clicks) "/" referral)
@ -17,17 +23,21 @@
(fx/defn handle-error
{:events [::on-error]}
[_ error]
{:utils/show-popup {:title "Request failed"
[{:keys [db]} error]
{:db (assoc-in db [:acquisition :network-status]
(get network-statuses :error))
:utils/show-popup {:title (i18n/label :t/http-gateway-error)
:content (str error)}})
(fx/defn handle-acquisition
{:events [::handle-acquisition]}
[{:keys [db] :as cofx} {:keys [message on-success method url]}]
(let [msg (types/clj->json message)]
{::json-rpc/call [{:method (json-rpc/call-ext-method (waku/enabled? cofx) "signMessageWithChatKey")
{:db (assoc-in db [:acquisition :network-status]
(get network-statuses :initiated))
::json-rpc/call [{:method (json-rpc/call-ext-method (waku/enabled? cofx) "signMessageWithChatKey")
:params [msg]
:on-error #(re-frame/dispatch [::on-error "Could not sign message"])
:on-error #(re-frame/dispatch [::on-error (i18n/label :t/sign-request-failed)])
:on-success #(re-frame/dispatch [::call-acquisition-gateway
{:chat-key (get-in db [:multiaccount :public-key])
:message msg
@ -36,29 +46,37 @@
:on-success on-success} %])}]}))
(fx/defn call-acquisition-gateway
{:events [::call-acquisition-gateway]}
[cofx
{:keys [chat-key message on-success type url method] :as kek}
[{:keys [db]}
{:keys [chat-key message on-success type url method]}
sig]
(let [payload {:chat_key chat-key
:msg message
:sig sig
:version 2}]
{:http-post {:url url
:opts {:headers {"Content-Type" "application/json"}
:method method}
:data (types/clj->json payload)
:success-event-creator (fn [response]
[on-success (types/json->clj (get response :response-body))])
:failure-event-creator (fn [error]
[::on-error (:error (types/json->clj (get error :response-body)))])}}))
{:db (assoc-in db [:acquisition :network-status]
(get network-statuses :in-flight))
:http-post {:url url
:opts {:headers {"Content-Type" "application/json"}
:method method}
:data (types/clj->json payload)
:on-success (fn [response]
(re-frame/dispatch [:set-in [:acquisition :network-status]]
(get network-statuses :success))
(re-frame/dispatch (conj on-success (types/json->clj (get response :response-body)))))
:on-error (fn [error]
(re-frame/dispatch [::on-error (:error (types/json->clj (get error :response-body)))]))}}))
(fx/defn get-referrer
[cofx referrer on-success handled-error handle-error]
{:http-get {:url (get-url :clicks referrer)
:success-event-creator (fn [response]
(on-success (types/json->clj response)))
:failure-event-creator (fn [response]
(let [error (types/json->clj response)]
(if (handled-error error)
(handle-error error)
[::on-error (:error error)])))}})
{:http-get {:url (get-url :clicks referrer)
:on-success (fn [response]
(re-frame/dispatch (on-success (types/json->clj response))))
:on-error (fn [response]
(let [error (types/json->clj response)]
(if (handled-error error)
(handle-error error)
(re-frame/dispatch [::on-error (:error error)]))))}})
(re-frame/reg-sub
::network-status
(fn [db]
(get-in db [:acquisition :network-status])))

View File

@ -18,7 +18,7 @@
[status-im.utils.platform :as platform]
[status-im.utils.random :as random]
[status-im.utils.types :as types]
[status-im.utils.universal-links.core :as universal-links]
[status-im.utils.universal-links.utils :as links]
[taoensso.timbre :as log]
[status-im.signing.core :as signing]
[status-im.multiaccounts.update.core :as multiaccounts.update]
@ -213,8 +213,8 @@
(fx/defn handle-message-link
[cofx link]
(if (universal-links/universal-link? link)
(universal-links/handle-url cofx link)
(if (links/universal-link? link)
{:dispatch [:universal-links/handle-url link]}
{:browser/show-browser-selection link}))
(fx/defn update-browser-on-nav-change
@ -240,10 +240,10 @@
(fx/defn navigation-state-changed
[cofx event error?]
(let [{:strs [url loading title]} (js->clj event)
deep-link? (universal-links/deep-link? url)]
(if (universal-links/universal-link? url)
deep-link? (links/deep-link? url)]
(if (links/universal-link? url)
(when-not (and deep-link? platform/ios?) ;; ios webview handles this
(universal-links/handle-url cofx url))
{:dispatch [:universal-links/handle-url url]})
(fx/merge cofx
(update-browser-option :loading? loading)
(update-browser-name title)
@ -256,8 +256,8 @@
[cofx url]
(let [browser (get-current-browser (:db cofx))
normalized-url (http/normalize-and-decode-url url)]
(if (universal-links/universal-link? normalized-url)
(universal-links/handle-url cofx normalized-url)
(if (links/universal-link? normalized-url)
{:dispatch [:universal-links/handle-url normalized-url]}
(fx/merge cofx
(update-browser-option :url-editing? false)
(update-browser-history browser normalized-url)
@ -272,8 +272,8 @@
browser {:browser-id (random/id)
:history-index 0
:history [normalized-url]}]
(if (universal-links/universal-link? normalized-url)
(universal-links/handle-url cofx normalized-url)
(if (links/universal-link? normalized-url)
{:dispatch [:universal-links/handle-url normalized-url]}
(fx/merge cofx
{:db (assoc db :browser/options
{:browser-id (:browser-id browser)})}
@ -472,7 +472,7 @@
(status/clear-web-data)))
(defn share-link [url]
(let [link (universal-links/generate-link :browse :external url)
(let [link (links/generate-link :browse :external url)
message (i18n/label :t/share-dapp-text {:link link})]
(list-selection/open-share {:message message})))

View File

@ -242,7 +242,7 @@
[cofx chat-id]
(fx/merge cofx
(preload-chat-data chat-id)
(navigation/navigate-to-cofx :chat {})))
(navigation/navigate-to-cofx :chat-stack {:screen :chat})))
(fx/defn start-chat
"Start a chat, making sure it exists"

View File

@ -57,7 +57,6 @@
[status-im.constants :as constants]
[status-im.native-module.core :as status]
[status-im.ui.components.permissions :as permissions]
[status-im.utils.http :as http]
[status-im.utils.utils :as utils]
status-im.ui.components.bottom-sheet.core
status-im.ui.screens.add-new.new-chat.events
@ -65,6 +64,7 @@
status-im.ui.screens.group.events
status-im.utils.universal-links.events
status-im.search.core
status-im.http.core
status-im.ui.screens.profile.events
status-im.chat.models.images
status-im.ui.screens.privacy-and-security-settings.events))
@ -1107,55 +1107,6 @@
(when (or (nil? cur-theme) (zero? cur-theme))
{::multiaccounts/switch-theme (if (= :dark theme) 2 1)}))))
(defn- http-get [{:keys [url response-validator success-event-creator failure-event-creator timeout-ms]}]
(let [on-success #(re-frame/dispatch (success-event-creator %))
on-error (when failure-event-creator #(re-frame/dispatch (failure-event-creator %)))
opts {:valid-response? response-validator
:timeout-ms timeout-ms}]
(http/get url on-success on-error opts)))
(re-frame/reg-fx
:http-get
http-get)
(defn- http-raw-get [{:keys [url success-event-creator failure-event-creator timeout-ms]}]
(let [on-success #(when-let [event (success-event-creator %)] (re-frame/dispatch event))
on-error (when failure-event-creator #(re-frame/dispatch (failure-event-creator %)))
opts {:timeout-ms timeout-ms}]
(http/raw-get url on-success on-error opts)))
(re-frame/reg-fx
:http-raw-get
http-raw-get)
(re-frame/reg-fx
:http-get-n
(fn [calls]
(doseq [call calls]
(http-get call))))
(defn- http-post [{:keys [url data response-validator success-event-creator failure-event-creator timeout-ms opts]}]
(let [on-success #(re-frame/dispatch (success-event-creator %))
on-error (when failure-event-creator #(re-frame/dispatch (failure-event-creator %)))
all-opts (assoc opts
:valid-response? response-validator
:timeout-ms timeout-ms)]
(http/post url data on-success on-error all-opts)))
(re-frame/reg-fx
:http-post
http-post)
(defn- http-raw-post [{:keys [url body response-validator on-success on-error timeout-ms opts]}]
(let [all-opts (assoc opts
:valid-response? response-validator
:timeout-ms timeout-ms)]
(http/raw-post url body on-success on-error all-opts)))
(re-frame/reg-fx
:http-raw-post
http-raw-post)
(re-frame/reg-fx
:request-permissions-fx
(fn [options]

View File

@ -0,0 +1,26 @@
(ns status-im.http.core
(:require [status-im.utils.http :as http]
[re-frame.core :as re-frame]))
(re-frame/reg-fx
:http-get
(fn [{:keys [url response-validator on-success on-error timeout-ms]}]
(let [opts {:valid-response? response-validator
:timeout-ms timeout-ms}]
(http/get url on-success on-error opts))))
(re-frame/reg-fx
:http-get-n
(fn [calls]
(doseq [{:keys [url response-validator on-success on-error timeout-ms]} calls]
(let [opts {:valid-response? response-validator
:timeout-ms timeout-ms}]
(http/get url on-success on-error opts)))))
(re-frame/reg-fx
:http-post
(fn [{:keys [url data response-validator on-success on-error timeout-ms opts]}]
(let [all-opts (assoc opts
:valid-response? response-validator
:timeout-ms timeout-ms)]
(http/post url data on-success on-error all-opts))))

View File

@ -1,6 +1,7 @@
(ns status-im.ipfs.core
(:refer-clojure :exclude [cat])
(:require [status-im.utils.fx :as fx]))
(:require [re-frame.core :as re-frame]
[status-im.utils.fx :as fx]))
;; we currently use an ipfs gateway but this detail is not relevant
;; outside of this namespace
@ -9,40 +10,13 @@
(fx/defn cat
[cofx {:keys [hash on-success on-failure]}]
{:http-raw-get (cond-> {:url (str ipfs-cat-url hash)
:timeout-ms 5000
:success-event-creator
(fn [{:keys [status body]}]
(if (= 200 status)
(on-success body)
(when on-failure
(on-failure status))))}
on-failure
(assoc :failure-event-creator on-failure))})
(defn- parse-ipfs-add-response
[response]
(when response
(let [{:keys [Name Hash Size]} (js->clj (js/JSON.parse response)
:keywordize-keys true)]
{:name Name
:hash Hash
:size Size})))
(fx/defn add
"Add `value` on ipfs, and returns its b58 encoded CID"
[cofx {:keys [value on-success on-failure opts timeout-ms]}]
(let [formdata (doto (js/FormData.)
;; the key is ignored so there is no need to provide one
(.append "file" value))]
{:http-raw-post {:url ipfs-add-url
:body formdata
:opts opts
:timeout-ms (or 25000 timeout-ms)
:on-failure on-failure
:on-success
(fn [{:keys [status body]}]
(if (= 200 status)
(on-success (parse-ipfs-add-response body))
(when on-failure
(on-failure status))))}}))
{:http-get (cond-> {:url (str ipfs-cat-url hash)
:timeout-ms 5000
:on-success (fn [{:keys [status body]}]
(if (= 200 status)
(re-frame/dispatch (on-success body))
(when on-failure
(re-frame/dispatch (on-failure status)))))}
on-failure
(assoc :on-error
#(re-frame/dispatch (on-failure %))))})

View File

@ -90,31 +90,31 @@
[{:keys [db] :as cofx} network-id]
(if-let [config (get-in db [:networks/networks network-id :config])]
(if-let [upstream-url (get-in config [:UpstreamConfig :URL])]
{:http-post {:url upstream-url
:data (types/clj->json [{:jsonrpc "2.0"
:method "web3_clientVersion"
:id 1}
{:jsonrpc "2.0"
:method "net_version"
:id 2}])
:opts {:headers {"Content-Type" "application/json"}}
:success-event-creator (fn [{:keys [response-body]}]
(let [responses (http/parse-payload response-body)
client-version (:result (first responses))
expected-network-id (:NetworkId config)
rpc-network-id (when-let [res (:result (second responses))]
(js/parseInt res))]
(if (and client-version network-id
(= expected-network-id rpc-network-id))
[::connect-success network-id]
[::connect-failure (if (not= expected-network-id rpc-network-id)
(i18n/label :t/network-invalid-network-id)
(i18n/label :t/network-invalid-url))])))
:failure-event-creator (fn [{:keys [response-body status-code]}]
(let [reason (if status-code
(i18n/label :t/network-invalid-status-code {:code status-code})
(str response-body))]
[::connect-failure reason]))}}
{:http-post {:url upstream-url
:data (types/clj->json [{:jsonrpc "2.0"
:method "web3_clientVersion"
:id 1}
{:jsonrpc "2.0"
:method "net_version"
:id 2}])
:opts {:headers {"Content-Type" "application/json"}}
:on-success (fn [{:keys [response-body]}]
(let [responses (http/parse-payload response-body)
client-version (:result (first responses))
expected-network-id (:NetworkId config)
rpc-network-id (when-let [res (:result (second responses))]
(js/parseInt res))]
(if (and client-version network-id
(= expected-network-id rpc-network-id))
(re-frame/dispatch [::connect-success network-id])
(re-frame/dispatch [::connect-failure (if (not= expected-network-id rpc-network-id)
(i18n/label :t/network-invalid-network-id)
(i18n/label :t/network-invalid-url))]))))
:on-error (fn [{:keys [response-body status-code]}]
(let [reason (if status-code
(i18n/label :t/network-invalid-status-code {:code status-code})
(str response-body))]
(re-frame/dispatch [::connect-failure reason])))}}
(connect-success cofx network-id))
(connect-failure cofx "A network with the specified id doesn't exist")))

View File

@ -142,9 +142,9 @@
(fx/defn load-pack
[cofx url id price]
{:http-get {:url url
:success-event-creator
:on-success
(fn [o]
[:stickers/load-sticker-pack-success o id price])}})
(re-frame/dispatch [:stickers/load-sticker-pack-success o id price]))}})
(fx/defn load-packs
[{:keys [db]}]

View File

@ -1,88 +1,19 @@
(ns status-im.ui.components.invite.advertiser
(:require [re-frame.core :as re-frame]
[quo.react-native :as rn]
[quo.core :as quo]
(:require [status-im.ui.components.invite.modal :as modal]
[re-frame.core :as re-frame]
[status-im.i18n :as i18n]
[status-im.ui.components.invite.events :as invite]
[quo.design-system.colors :as colors]
[status-im.ui.components.invite.utils :as utils]
[status-im.acquisition.core :as acquisition]
[status-im.acquisition.advertiser :as advertiser]))
(defn perk [{name :name
{source :source} :icon} value]
[rn/view {:style {:flex-direction :row
:align-items :center
:padding-vertical 4}}
[rn/view {:style {:flex-direction :row}}
[rn/image {:source (if (fn? source) (source) source)
:style {:width 20
:height 20
:margin-right 8}}]
[quo/text {:size :small
:weight :medium}
(str value " ")]
[quo/text {:size :small
:weight :medium}
name]]])
(defn token-icon-style [idx]
{:align-items :center
:shadow-radius 16
:shadow-opacity 1
:shadow-color (:shadow-01 @colors/theme)
:shadow-offset {:width 0 :height 4}
:width 40
:height 40
:border-radius 20
:left (* idx -20)})
(defn accept-popover []
(fn []
(let [pack @(re-frame/subscribe [::invite/starter-pack])
tokens (utils/transform-tokens pack)]
[rn/view {:style {:align-items :center
:padding-vertical 16
:padding-horizontal 16}}
[rn/view {:flex-direction :row
:height 40
:left 10}
(for [[{name :name
{source :source} :icon} _ i] tokens]
^{:key name}
[rn/view {:style (token-icon-style i)}
[rn/image {:source (if (fn? source) (source) source)
:style {:width 40
:height 40}}]])]
[rn/view {:style {:padding 8}}
[quo/text {:style {:margin-bottom 8}
:align :center
:size :x-large}
(i18n/label :t/advertiser-starter-pack-title)]
[quo/text {:align :center}
(i18n/label :t/advertiser-starter-pack-description)]]
[rn/view {:style {:border-radius 8
:border-width 1
:border-color (:ui-02 @colors/theme)
:width "100%"
:margin-vertical 8
:padding-vertical 8
:padding-horizontal 12}}
(for [[k v] tokens]
^{:key (:name k)}
[perk k v])]
[rn/view {:style {:margin-vertical 8}}
[quo/button {:on-press #(re-frame/dispatch [::advertiser/decision :accept])}
(i18n/label :t/advertiser-starter-pack-accept)]]
[quo/button {:type :secondary
:on-press #(re-frame/dispatch [::advertiser/decision :decline])}
(i18n/label :t/advertiser-starter-pack-decline)]
[rn/view {:padding-vertical 8}
[quo/text {:color :secondary
:align :center
:size :small}
(i18n/label :t/invite-privacy-policy1)]
[quo/text {:color :link
:align :center
:size :small
:on-press #(re-frame/dispatch [::invite/terms-and-conditions])}
(i18n/label :t/invite-privacy-policy2)]]])))
(let [{:keys [rewardable]} @(re-frame/subscribe [::acquisition/metadata])]
[modal/popover {:on-accept #(re-frame/dispatch [::advertiser/decision :accept])
:on-decline #(re-frame/dispatch [::advertiser/decision :decline])
:has-reward rewardable
:accept-label (i18n/label :t/advertiser-starter-pack-accept)
:title (if rewardable
(i18n/label :t/advertiser-starter-pack-title)
(i18n/label :t/advertiser-title))
:description (if rewardable
(i18n/label :t/advertiser-starter-pack-description)
(i18n/label :t/advertiser-description))}]))

View File

@ -9,6 +9,7 @@
[status-im.acquisition.chat :as acquisition]
[status-im.ui.components.invite.events :as invite]
[status-im.i18n :as i18n]
[status-im.acquisition.gateway :as gateway]
[status-im.ui.components.invite.style :as styles]))
(defn messages-wrapper []
@ -57,7 +58,7 @@
tokens (utils/transform-tokens pack)
reward-text (cstr/join ", " (map (comp name :symbol first) tokens))]
[rn/view {:style (starter-pack-style)}
[rn/view {:style {:padding-right 16}}
[rn/view {:style (styles/reward-tokens-icons (count tokens))}
(doall
(for [[{name :name
{source :source} :icon} _ idx] tokens]
@ -88,6 +89,9 @@
(defn reward-messages []
(let [pending-invite @(re-frame/subscribe [::invite/pending-chat-invite])
loading (#{(get gateway/network-statuses :initiated)
(get gateway/network-statuses :in-flight)}
@(re-frame/subscribe [::gateway/network-status]))
messages [{:content [{:type :text
:value "👋"}]}
{:content [{:type :author
@ -97,6 +101,7 @@
{:type :pack}
{:type :button
:value [quo/button {:type :secondary
:loading loading
:on-press #(re-frame/dispatch [::acquisition/accept-pack])}
(i18n/label :t/invite-chat-accept)]}]}
{:content [{:type :text

View File

@ -0,0 +1,17 @@
(ns status-im.ui.components.invite.dapp
(:require [status-im.ui.components.invite.modal :as modal]
[re-frame.core :as re-frame]
[status-im.acquisition.core :as acquisition]
[status-im.i18n :as i18n]
[status-im.acquisition.dapp :as dapp]))
(defn accept-popover []
(let [{:keys [rewardable]} @(re-frame/subscribe [::acquisition/metadata])]
[modal/popover {:on-accept #(re-frame/dispatch [::dapp/decision :accept])
:on-decline #(re-frame/dispatch [::dapp/decision :decline])
:has-reward rewardable
:accept-label (i18n/label :t/dapp-starter-pack-accept)
:title (i18n/label :t/dapp-starter-pack-title)
:description (i18n/label :t/dapp-starter-pack-description)}]))

View File

@ -1,17 +1,23 @@
(ns status-im.ui.components.invite.events
(:require [re-frame.core :as re-frame]
[reagent.ratom :refer [make-reaction]]
[taoensso.timbre :as log]
[status-im.utils.fx :as fx]
[status-im.utils.config :as config]
[status-im.ethereum.abi-spec :as abi-spec]
[status-im.ethereum.json-rpc :as json-rpc]
[status-im.i18n :as i18n]
[status-im.signing.core :as signing]
[status-im.ethereum.core :as ethereum]
[status-im.ui.components.react :as react]
[status-im.navigation :as navigation]
[status-im.utils.universal-links.core :as universal-links]
[status-im.utils.universal-links.utils :as universal-links]
[status-im.acquisition.core :as acquisition]
[status-im.utils.money :as money]))
[status-im.acquisition.persistance :as persistence]
[status-im.utils.money :as money]
[status-im.ui.components.bottom-sheet.core :as bottom-sheet]))
(def privacy-policy-link "https://get.status.im")
(def privacy-policy-link "https://status.im/referral-program/terms-and-conditions")
(re-frame/reg-fx
::share
@ -28,7 +34,7 @@
share-link (cond-> profile-link
invite-id
(str "?invite=" invite-id))
message (str "Hey join me on Status: " share-link)]
message (i18n/label :t/join-me {:url share-link})]
{::share {:message message}}))
(fx/defn generate-invite
@ -37,7 +43,7 @@
(acquisition/handle-registration cofx
{:message {:address address
:interaction_address (get-in db [:multiaccount :public-key])}
:on-success ::share-link}))
:on-success [::share-link]}))
(re-frame/reg-sub
::pending-chat-invite
@ -48,7 +54,8 @@
:as chat-referrer} (get-in db [:acquisition :chat-referrer chat-id])]
(and chat-referrer
(not attributed)
(nil? flow-state)))))
(or (= flow-state (get persistence/referrer-state :accepted))
(nil? flow-state))))))
(fx/defn go-to-invite
{:events [::open-invite]}
@ -74,8 +81,30 @@
[_]
{::terms-and-conditions nil})
;; Invite reward
(fx/defn redeem-success
{:events [::redeem-success]}
[{:keys [db]} account]
{:db (assoc-in db [:acquisition :accounts account :bonuses] 0)})
(fx/defn redeem-error
{:events [::redeem-error]}
[cofx error]
{:utils/show-popup {:title "Error"
:content error}})
(fx/defn redeem-bonus
{:events [::redeem-bonus]}
[{:keys [db] :as cofx} {:keys [address]}]
(fx/merge cofx
(bottom-sheet/hide-bottom-sheet)
(signing/sign
{:tx-obj {:to (get-in db [:acquisition :contract])
:from address
:data (abi-spec/encode "withdrawAttributions()" [])}
:on-result [::redeem-success address]
:on-error [::redeem-error]})))
;; Invite reward
(re-frame/reg-sub
:invite/accounts-reward
@ -89,6 +118,14 @@
(get accounts account)))
(defn- get-reward [contract address on-success]
(json-rpc/eth-call
{:contract contract
:method "pendingAttributionCnt(address)"
:params [address]
:outputs ["uint256"]
:on-success (fn [[response]]
(log/info "Attribution count for" address "=" response)
(on-success :attribution response))})
(json-rpc/eth-call
{:contract contract
:method "getReferralReward(address,bool)"
@ -97,6 +134,7 @@
:outputs ["uint256" "uint256" "uint256" "uint256"]
:on-success (fn [[eth-amount tokens-count max-threshold attrib-count]]
(on-success :reward [eth-amount tokens-count max-threshold attrib-count])
(log/info "Reward data for" address "=" eth-amount tokens-count max-threshold attrib-count)
(dotimes [id tokens-count]
(json-rpc/eth-call
{:contract contract
@ -104,6 +142,7 @@
:params [address false id]
:outputs ["address" "uint256"]
:on-success (fn [token-data]
(log/info "Token data for" address "token id" id "=" token-data)
(on-success :token token-data))})))}))
(re-frame/reg-fx
@ -115,25 +154,35 @@
(fx/defn default-reward-success
{:events [::default-reward-success]}
[{:keys [db]} type data]
(if (= :reward type)
(case type
:reward
(let [[eth-amount tokens-count max-threshold attrib-count] data]
{:db (assoc-in db [:acquisition :referral-reward] {:eth-amount (money/wei->ether eth-amount)
:tokens-count tokens-count
:max-threshold max-threshold
:attrib-count attrib-count})})
:token
(let [[address amount] data]
{:db (assoc-in db [:acquisition :referral-reward :tokens address] (money/wei->ether amount))})))
{:db (assoc-in db [:acquisition :referral-reward :tokens address] (money/wei->ether amount))})
:attribution
{:db (assoc-in db [:acquisition :referral-reward :bonuses] data)}))
(fx/defn get-reward-success
{:events [::get-reward-success]}
[{:keys [db]} account type data]
(if (= :reward type)
(case type
:reward
(let [[eth-amount _ max-threshold attrib-count] data]
{:db (assoc-in db [:acquisition :accounts account] {:eth-amount (money/wei->ether eth-amount)
:max-threshold max-threshold
:attrib-count attrib-count})})
{:db (update-in db [:acquisition :accounts account] merge {:eth-amount (money/wei->ether eth-amount)
:max-threshold max-threshold
:attrib-count attrib-count})})
:token
(let [[address amount] data]
{:db (assoc-in db [:acquisition :accounts account :tokens address] (money/wei->ether amount))})))
{:db (assoc-in db [:acquisition :accounts account :tokens address] (money/wei->ether amount))})
:attribution
{:db (assoc-in db [:acquisition :accounts account :bonuses] data)}))
(re-frame/reg-sub-raw
::default-reward
@ -156,7 +205,6 @@
:tokens (zipmap tokens
(map money/wei->ether tokens-amount))
:sticker-packs sticker-packs})})
(re-frame/reg-sub-raw
::starter-pack
(fn [db]

View File

@ -0,0 +1,77 @@
(ns status-im.ui.components.invite.modal
(:require [re-frame.core :as re-frame]
[quo.react-native :as rn]
[quo.core :as quo]
[status-im.i18n :as i18n]
[status-im.ui.components.invite.style :as styles]
[status-im.ui.components.invite.events :as invite]
[status-im.acquisition.gateway :as gateway]
[status-im.ui.components.invite.utils :as utils]))
(defn perk [{name :name
{source :source} :icon} value]
[rn/view {:style {:flex-direction :row
:align-items :center
:padding-vertical 4}}
[rn/view {:style {:flex-direction :row}}
[rn/image {:source (if (fn? source) (source) source)
:style {:width 20
:height 20
:margin-right 8}}]
[quo/text {:size :small
:weight :medium}
(str value " ")]
[quo/text {:size :small
:weight :medium}
name]]])
(defn popover [{:keys [on-accept has-reward on-decline accept-label title description]}]
(fn []
(let [pack (when has-reward @(re-frame/subscribe [::invite/starter-pack]))
loading (#{(get gateway/network-statuses :initiated)
(get gateway/network-statuses :in-flight)}
@(re-frame/subscribe [::gateway/network-status]))
tokens (when has-reward (utils/transform-tokens pack))]
[rn/view {:style {:align-items :center
:padding-vertical 16
:padding-horizontal 16}}
(when has-reward
[rn/view {:style (styles/modal-tokens-icons-style (count tokens))}
(for [[{name :name
{source :source} :icon} _ i] tokens]
^{:key name}
[rn/view {:style (styles/modal-token-icon-style i)}
[rn/image {:source (if (fn? source) (source) source)
:style {:width 40
:height 40}}]])])
[rn/view {:style {:padding 8}}
(when title
[quo/text {:style {:margin-bottom 8}
:align :center
:size :x-large}
title])
[quo/text {:align :center}
description]]
(when has-reward
[rn/view {:style (styles/modal-perks-container)}
(for [[k v] tokens]
^{:key (:name k)}
[perk k v])])
[rn/view {:style {:margin-vertical 8}}
[quo/button {:on-press on-accept
:loading loading}
accept-label]]
[rn/view {:style {:opacity (if loading 0 1)}}
[quo/button {:type :secondary
:on-press on-decline}
(i18n/label :t/advertiser-starter-pack-decline)]]
[rn/view {:padding-vertical 8}
[quo/text {:color :secondary
:align :center
:size :small}
(i18n/label :t/invite-privacy-policy1)]
[quo/text {:color :link
:align :center
:size :small
:on-press #(re-frame/dispatch [::invite/terms-and-conditions])}
(i18n/label :t/invite-privacy-policy2)]]])))

View File

@ -32,3 +32,73 @@
{:margin-right 16
:width 40
:height (- (* 40 c) (* 20 (dec c)))})
(defn home-token-icon-style [idx]
{:align-items :center
:shadow-radius 16
:shadow-opacity 1
:shadow-color (:shadow-01 @colors/theme)
:shadow-offset {:width 0 :height 4}
:width 20
:height 20
:border-radius 20
:position :absolute
:top 0
:left (* idx 10)})
(defn home-tokens-icons [c]
{:height 20
:margin-horizontal 6
:width (- (* 20 c) (* 10 (dec c)))})
(defn invite-instructions []
{:border-top-width 1
:border-top-color (:ui-01 @colors/theme)
:border-bottom-width 1
:border-bottom-color (:ui-01 @colors/theme)
:padding-top (:small spacing/spacing)})
(defn invite-instructions-title []
(merge
(:tiny spacing/padding-vertical)
(:base spacing/padding-horizontal)))
(defn invite-instructions-content []
(merge (:tiny spacing/padding-vertical)
(:base spacing/padding-horizontal)))
(defn invite-warning []
(merge
(:tiny spacing/padding-vertical)
(:base spacing/padding-horizontal)
{:background-color (:warning-02 @colors/theme)
:border-top-width 1
:border-top-color (:warning-01 @colors/theme)
:border-bottom-width 1
:border-bottom-color (:warning-01 @colors/theme)}))
(defn modal-token-icon-style [idx]
{:align-items :center
:shadow-radius 16
:shadow-opacity 1
:shadow-color (:shadow-01 @colors/theme)
:shadow-offset {:width 0 :height 4}
:width 40
:height 40
:border-radius 20
:position :absolute
:top 0
:left (* idx 20)})
(defn modal-tokens-icons-style [c]
{:height 40
:width (- (* 40 c) (* 20 (dec c)))})
(defn modal-perks-container []
{:border-radius 8
:border-width 1
:border-color (:ui-02 @colors/theme)
:width "100%"
:margin-vertical 8
:padding-vertical 8
:padding-horizontal 12})

View File

@ -6,7 +6,6 @@
[status-im.ui.components.toolbar :as toolbar]
[status-im.utils.utils :as utils]
[status-im.i18n :as i18n]
[status-im.ethereum.tokens :as tokens]
[quo.design-system.spacing :as spacing]
[quo.design-system.colors :as colors]
[status-im.ui.components.invite.style :as styles]
@ -32,33 +31,42 @@
;; Select account sheet
(defn- render-account [current-account change-account]
(fn [account]
(let [{:keys [max-threshold attrib-count]}
(let [{:keys [max-threshold attrib-count bonuses]
:or {bonuses 0}}
@(re-frame/subscribe [:invite/account-reward (:address account)])]
[quo/list-item
{:theme :accent
:active (= (:address current-account) (:address account))
:disabled (and max-threshold attrib-count
(< max-threshold (inc attrib-count)))
:accessory :radio
:icon [chat-icon/custom-icon-view-list (:name account) (:color account)]
:title (:name account)
:subtitle [:<>
[quo/text {:monospace true
:color :secondary}
(utils/get-shortened-checksum-address (:address account))]
[threshold-badge max-threshold attrib-count]]
:on-press #(change-account account)}])))
[:<>
[quo/list-item
{:theme :accent
:active (= (:address current-account) (:address account))
:disabled (and max-threshold attrib-count
(< max-threshold (inc attrib-count)))
:accessory :radio
:icon [chat-icon/custom-icon-view-list (:name account) (:color account)]
:title (:name account)
:subtitle [:<>
[quo/text {:monospace true
:color :secondary}
(utils/get-shortened-checksum-address (:address account))]
[threshold-badge max-threshold attrib-count]]
:on-press #(change-account account)}]
[quo/list-item
{:theme :accent
:disabled (not (pos? bonuses))
:icon :main-icons/arrow-down
:title (i18n/label :t/redeem-now)
:subtitle (i18n/label :t/redeem-amount {:quantity bonuses})
:on-press #(re-frame/dispatch [::invite.events/redeem-bonus account])}]
[quo/separator {:style {:margin-vertical 10}}]])))
(defn- accounts-list [accounts current-account change-account]
(fn []
[rn/view {:flex 1}
[rn/view {:style (merge (:base spacing/padding-horizontal)
(:tiny spacing/padding-vertical))}
[quo/text {:align :center}
(i18n/label :t/invite-select-account)]]
[rn/flat-list {:data accounts
:key-fn :address
:render-fn (render-account current-account change-account)}]]))
[rn/view {:flex 1}
[rn/view {:style (merge (:base spacing/padding-horizontal)
(:tiny spacing/padding-vertical))}
[quo/text {:align :center}
(i18n/label :t/invite-select-account)]]
[rn/flat-list {:data accounts
:key-fn :address
:render-fn (render-account current-account change-account)}]])
;; Invite sheet
@ -92,16 +100,13 @@
:description :t/invite-instruction-fourth}])
(defn- referral-steps []
[rn/view {:style (merge
(:tiny spacing/padding-vertical)
(:base spacing/padding-horizontal)
{:border-bottom-width 1
:border-bottom-color (:ui-01 @colors/theme)})}
[rn/view {:style {:padding-top (:small spacing/spacing)
:padding-bottom (:x-tiny spacing/spacing)}}
[rn/view {:style (styles/invite-instructions)}
[rn/view {:style (styles/invite-instructions-title)}
[quo/text {:color :secondary}
(i18n/label :t/invite-instruction)]]
[rn/view {:flex 1}
[rn/view {:style (styles/invite-warning)}
[quo/text (i18n/label :t/invite-warning)]]
[rn/view {:style (styles/invite-instructions-content)}
(for [s steps-values]
^{:key (str (:number s))}
[step s])]])
@ -112,26 +117,33 @@
(re-frame/dispatch [:bottom-sheet/hide])
(change-account a))]))
(defn- referral-account []
(fn [{:keys [account accounts change-account]}]
(let [{:keys [max-threshold attrib-count]}
@(re-frame/subscribe [:invite/account-reward (:address account)])]
[rn/view {:style (:tiny spacing/padding-vertical)}
[rn/view {:style (merge (:base spacing/padding-horizontal)
(:x-tiny spacing/padding-vertical))}
[quo/text {:color :secondary}
(i18n/label :t/invite-receive-account)]]
[quo/list-item
{:icon [chat-icon/custom-icon-view-list (:name account) (:color account)]
:title (:name account)
:subtitle [:<>
[quo/text {:monospace true
:color :secondary}
(utils/get-shortened-checksum-address (:address account))]
[threshold-badge max-threshold attrib-count]]
:on-press #(re-frame/dispatch
[:bottom-sheet/show-sheet
{:content (bottom-sheet-content accounts account change-account)}])}]])))
(defn- referral-account [{:keys [account accounts change-account]}]
(let [{:keys [max-threshold bonuses attrib-count]
:or {bonuses 0}}
@(re-frame/subscribe [:invite/account-reward (:address account)])]
[rn/view {:style (:tiny spacing/padding-vertical)}
[rn/view {:style (merge (:base spacing/padding-horizontal)
(:x-tiny spacing/padding-vertical))}
[quo/text {:color :secondary}
(i18n/label :t/invite-receive-account)]]
[quo/list-item
{:icon [chat-icon/custom-icon-view-list (:name account) (:color account)]
:title (:name account)
:subtitle [:<>
[quo/text {:monospace true
:color :secondary}
(utils/get-shortened-checksum-address (:address account))]
[threshold-badge max-threshold attrib-count]]
:on-press #(re-frame/dispatch
[:bottom-sheet/show-sheet
{:content (bottom-sheet-content accounts account change-account)}])}]
[quo/list-item
{:theme :accent
:disabled (not (pos? bonuses))
:icon :main-icons/arrow-down
:title (i18n/label :t/redeem-now)
:subtitle (i18n/label :t/redeem-amount {:quantity bonuses})
:on-press #(re-frame/dispatch [::invite.events/redeem-bonus account])}]]))
(defn reward-item [data description]
(let [tokens (transform-tokens data)
@ -226,25 +238,30 @@
[quo/button {:on-press #(re-frame/dispatch [::invite.events/share-link nil])
:accessibility-label :invite-friends-button}
(i18n/label :t/invite-friends)]]]
(let [reward @(re-frame/subscribe [::invite.events/default-reward])]
[rn/view {:style {:align-items :center}}
(let [pack @(re-frame/subscribe [::invite.events/default-reward])
tokens (transform-tokens pack)]
[rn/view {:style {:align-items :center
:padding-horizontal 8
:padding-vertical 8}}
[rn/view {:style (:tiny spacing/padding-vertical)}
[quo/button {:on-press #(re-frame/dispatch [::invite.events/open-invite])
:accessibility-label :invite-friends-button}
(i18n/label :t/invite-friends)]]
[rn/view {:style (merge (:tiny spacing/padding-vertical)
(:base spacing/padding-horizontal))}
(when reward
(when (seq tokens)
[rn/view {:style {:flex-direction :row
:align-items :center
:justify-content :center}}
[rn/view {:style (:tiny spacing/padding-horizontal)}
(when-let [{:keys [source]} (tokens/symbol->icon :SNT)]
[rn/image {:style {:width 20
:height 20}
:source (source)}])]
[rn/view {:style (styles/home-tokens-icons (count tokens))}
(for [[{name :name
{source :source} :icon} _ i] tokens]
^{:key name}
[rn/view {:style (styles/home-token-icon-style i)}
[rn/image {:source (if (fn? source) (source) source)
:style {:width 20
:height 20}}]])]
[quo/text {:align :center}
(i18n/label :t/invite-reward {:value (str (get reward :eth-amount) " ETH")})]])]])))
(i18n/label :t/invite-reward)]])]])))
(defn list-item [{:keys [accessibility-label]}]
(if-not @(re-frame/subscribe [::invite.events/enabled])
@ -257,16 +274,15 @@
(re-frame/dispatch [:bottom-sheet/hide])
(js/setTimeout
#(re-frame/dispatch [::invite.events/share-link nil]) 250))}]
(let [amount @(re-frame/subscribe [::invite.events/default-reward])]
[quo/list-item
{:theme :accent
:title (i18n/label :t/invite-friends)
:subtitle (i18n/label :t/invite-reward {:value (str (get amount :eth-amount) " ETH")})
:icon :main-icons/share
:accessibility-label accessibility-label
:on-press #(do
(re-frame/dispatch [:bottom-sheet/hide])
(re-frame/dispatch [::invite.events/open-invite]))}])))
[quo/list-item
{:theme :accent
:title (i18n/label :t/invite-friends)
:subtitle (i18n/label :t/invite-reward)
:icon :main-icons/share
:accessibility-label accessibility-label
:on-press #(do
(re-frame/dispatch [:bottom-sheet/hide])
(re-frame/dispatch [::invite.events/open-invite]))}]))

View File

@ -2,7 +2,7 @@
(:require [re-frame.core :as re-frame]
[quo.core :as quo]
[status-im.ui.components.react :as react]
[status-im.utils.universal-links.core :as links]
[status-im.utils.universal-links.utils :as links]
[status-im.ui.screens.chat.styles.main :as style]
[status-im.i18n :as i18n]
[status-im.ui.components.list-selection :as list-selection]

View File

@ -3,7 +3,7 @@
[status-im.i18n :as i18n]
[status-im.ui.components.react :as react]
[status-im.ui.components.list-selection :as list-selection]
[status-im.utils.universal-links.core :as universal-links]
[status-im.utils.universal-links.utils :as universal-links]
[status-im.ui.components.chat-icon.screen :as chat-icon]
[status-im.multiaccounts.core :as multiaccounts]
[status-im.ui.screens.chat.styles.message.sheets :as sheets.styles]

View File

@ -91,14 +91,14 @@
{:position :absolute
:width "100%"
:height 1
:top 9
:border-top-width 1
:border-color colors/gray-lighter})
:top 10
:background-color colors/gray-lighter})
(defn or-text []
{:width 40
:background-color colors/white
:font-size 12
:line-height 20
:text-align :center
:color colors/gray})

View File

@ -64,10 +64,10 @@
:on-press #(re-frame/dispatch [:multiaccounts.ui/hide-home-tooltip])
:accessibility-label :hide-home-button}
[icons/icon :main-icons/close-circle {:color colors/gray}]]]]
[react/view {:style {:padding-bottom 8}}
[react/view
[react/i18n-text {:style styles/no-chats-text :key :chat-and-transact}]]
[invite/button]
[react/view {:align-items :center :padding-top 8}
[react/view {:align-items :center}
[react/view {:style (styles/hr-wrapper)}]
[react/i18n-text {:style (styles/or-text) :key :or}]]
[react/view {:margin-top 16}

View File

@ -9,7 +9,8 @@
[status-im.ui.screens.wallet.request.views :as request]
[status-im.ui.screens.profile.user.views :as profile.user]
["react-native" :refer (BackHandler)]
[status-im.ui.components.invite.advertiser :as invite]
[status-im.ui.components.invite.advertiser :as advertiser.invite]
[status-im.ui.components.invite.dapp :as dapp.invite]
[status-im.ui.screens.multiaccounts.recover.views :as multiaccounts.recover]
[status-im.ui.screens.signing.views :as signing]
[status-im.ui.screens.biometric.views :as biometric]
@ -143,8 +144,11 @@
(= :frozen-card view)
[frozen-card/frozen-card]
(= :accept-invite view)
[invite/accept-popover]
(= :advertiser-invite view)
[advertiser.invite/accept-popover]
(= :dapp-invite view)
[dapp.invite/accept-popover]
:else
[view])]]]]])))})))

View File

@ -5,7 +5,7 @@
[status-im.ui.components.list-selection :as list-selection]
[status-im.utils.handlers :as handlers]
[status-im.utils.identicon :as identicon]
[status-im.utils.universal-links.core :as universal-links]
[status-im.utils.universal-links.utils :as universal-links]
[status-im.utils.fx :as fx]))
(handlers/register-handler-fx

View File

@ -14,7 +14,7 @@
[status-im.utils.platform :as platform]
[status-im.utils.config :as config]
[status-im.utils.gfycat.core :as gfy]
[status-im.utils.universal-links.core :as universal-links]
[status-im.utils.universal-links.utils :as universal-links]
[status-im.ui.components.profile-header.view :as profile-header])
(:require-macros [status-im.utils.views :as views]))

View File

@ -53,17 +53,16 @@
(when headers
{:headers headers}))))
(.then (fn [^js response]
(->
(.text response)
(.then (fn [response-body]
(let [ok? (.-ok response)
ok?' (if valid-response?
(and ok? (valid-response? response))
ok?)]
{:response-body response-body
:ok? ok?'
:status-text (.-statusText response)
:status-code (.-status response)}))))))
(-> (.text response)
(.then (fn [response-body]
(let [ok? (.-ok response)
ok?' (if valid-response?
(and ok? (valid-response? response))
ok?)]
{:response-body response-body
:ok? ok?'
:status-text (.-statusText response)
:status-code (.-status response)}))))))
(.then (fn [{:keys [ok?] :as data}]
(cond
(and on-success ok?)
@ -78,29 +77,6 @@
(on-error {:response-body error})
(utils/show-popup "Error" url (str error))))))))
(defn raw-get
"Performs an HTTP GET request and returns raw results :status :headers :body."
([url] (raw-get url nil))
([url on-success] (raw-get url on-success nil))
([url on-success on-error]
(raw-get url on-success on-error nil))
([url on-success on-error {:keys [timeout-ms]}]
(-> (fetch
url
(clj->js {:method "GET"
:headers {"Cache-Control" "no-cache"}
:timeout (or timeout-ms http-request-default-timeout-ms)}))
(.then (fn [^js response]
(->
(.text response)
(.then (fn [body]
(on-success {:status (.-status response)
:headers (response-headers response)
:body body}))))))
(.catch (or on-error
(fn [error]
(utils/show-popup "Error" url (str error))))))))
(defn get
"Performs an HTTP GET request"
([url] (get url nil))

View File

@ -117,6 +117,7 @@
(fx/defn handle-url
"Store url in the database if the user is not logged in, to be processed
on login, otherwise just handle it"
{:events [:universal-links/handle-url]}
[cofx url]
(if (multiaccounts.model/logged-in? cofx)
(route-url cofx url)

View File

@ -37,35 +37,3 @@
(with-redefs [re-frame/dispatch #(reset! actual %)]
(links/url-event-listener #js {})
(is (= nil @actual)))))))
(deftest universal-link-test
(testing "status-im://blah"
(testing "it returns true"
(is (links/universal-link? "status-im://blah"))))
(testing "status-im://blah"
(testing "it returns true"
(is (links/deep-link? "status-im://blah"))))
(testing "ethereum:0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7"
(testing "it returns true"
(is (links/deep-link? "ethereum:0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7"))))
(testing "http://join.status.im/blah"
(testing "it returns true"
(is (links/universal-link? "http://join.status.im/blah"))))
(testing "https://join.status.im/blah"
(testing "it returns true"
(is (links/universal-link? "https://join.status.im/blah"))))
(testing "unicode characters"
(testing "it returns false"
(is (not (links/universal-link? "https://join.status.im/browse/www.аррӏе.com")))))
(testing "not-status-im://blah"
(testing "it returns false"
(is (not (links/universal-link? "https://not.status.im/blah")))))
(testing "http://not.status.im/blah"
(testing "it returns false"
(is (not (links/universal-link? "https://not.status.im/blah")))))
(testing "https://not.status.im/blah"
(testing "it returns false"
(is (not (links/universal-link? "https://not.status.im/blah")))))
(testing "http://join.status.im/blah"
(testing "it returns false"
(is (not (links/deep-link? "http://join.status.im/blah"))))))

View File

@ -0,0 +1,25 @@
(ns status-im.utils.universal-links.utils
(:require [status-im.constants :as constants]
[goog.string :as gstring]))
;; domains should be without the trailing slash
(def domains {:external "https://join.status.im"
:internal "status-im:/"})
(def links {:public-chat "%s/%s"
:private-chat "%s/p/%s"
:user "%s/u/%s"
:browse "%s/b/%s"})
(defn universal-link? [url]
(boolean
(re-matches constants/regx-universal-link url)))
(defn deep-link? [url]
(boolean
(re-matches constants/regx-deep-link url)))
(defn generate-link [link-type domain-type param]
(gstring/format (get links link-type)
(get domains domain-type)
param))

View File

@ -0,0 +1,35 @@
(ns status-im.utils.universal-links.utils-test
(:require [cljs.test :refer-macros [deftest is testing]]
[status-im.utils.universal-links.utils :as links]))
(deftest universal-link-test
(testing "status-im://blah"
(testing "it returns true"
(is (links/universal-link? "status-im://blah"))))
(testing "status-im://blah"
(testing "it returns true"
(is (links/deep-link? "status-im://blah"))))
(testing "ethereum:0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7"
(testing "it returns true"
(is (links/deep-link? "ethereum:0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7"))))
(testing "http://join.status.im/blah"
(testing "it returns true"
(is (links/universal-link? "http://join.status.im/blah"))))
(testing "https://join.status.im/blah"
(testing "it returns true"
(is (links/universal-link? "https://join.status.im/blah"))))
(testing "unicode characters"
(testing "it returns false"
(is (not (links/universal-link? "https://join.status.im/browse/www.аррӏе.com")))))
(testing "not-status-im://blah"
(testing "it returns false"
(is (not (links/universal-link? "https://not.status.im/blah")))))
(testing "http://not.status.im/blah"
(testing "it returns false"
(is (not (links/universal-link? "https://not.status.im/blah")))))
(testing "https://not.status.im/blah"
(testing "it returns false"
(is (not (links/universal-link? "https://not.status.im/blah")))))
(testing "http://join.status.im/blah"
(testing "it returns false"
(is (not (links/deep-link? "http://join.status.im/blah"))))))

View File

@ -50,48 +50,48 @@
(handlers/register-handler-fx
:load-kitties
(fn [{db :db} [_ ids]]
{:db (update-in db [:collectibles] merge {ck (sorted-map-by >)})
{:db (update-in db [:collectibles] merge {ck (sorted-map-by >)})
:http-get-n (mapv (fn [id]
{:url (str "https://api.cryptokitties.co/kitties/" id)
:success-event-creator (fn [o]
[:load-collectible-success ck {id (http/parse-payload o)}])
:failure-event-creator (fn [o]
[:load-collectible-failure ck {id (http/parse-payload o)}])})
{:url (str "https://api.cryptokitties.co/kitties/" id)
:on-success (fn [o]
(re-frame/dispatch [:load-collectible-success ck {id (http/parse-payload o)}]))
:on-error (fn [o]
(re-frame/dispatch [:load-collectible-failure ck {id (http/parse-payload o)}]))})
ids)}))
(defmethod load-collectibles-fx ck [_ _ items-number address _]
{:http-get-n (mapv (fn [offset]
{:url (str "https://api.cryptokitties.co/kitties?limit=20&offset="
offset
"&owner_wallet_address="
address
"&parents=false")
:success-event-creator (fn [o]
[:load-kitties (map :id (:kitties (http/parse-payload o)))])
:failure-event-creator (fn [o]
[:load-collectibles-failure (http/parse-payload o)])
:timeout-ms 10000})
{:url (str "https://api.cryptokitties.co/kitties?limit=20&offset="
offset
"&owner_wallet_address="
address
"&parents=false")
:on-success (fn [o]
(re-frame/dispatch [:load-kitties (map :id (:kitties (http/parse-payload o)))]))
:on-error (fn [o]
(re-frame/dispatch [:load-collectibles-failure (http/parse-payload o)]))
:timeout-ms 10000})
(range 0 items-number 20))}) ;; Cryptokitties API limited to 20 items per request
;; Crypto Strikers
(def strikers :STRK)
(defmethod load-collectible-fx strikers [_ _ id]
{:http-get {:url (str "https://us-central1-cryptostrikers-prod.cloudfunctions.net/cards/" id)
:success-event-creator (fn [o]
[:load-collectible-success strikers {id (http/parse-payload o)}])
:failure-event-creator (fn [o]
[:load-collectible-failure strikers {id (http/parse-payload o)}])}})
{:http-get {:url (str "https://us-central1-cryptostrikers-prod.cloudfunctions.net/cards/" id)
:on-success (fn [o]
(re-frame/dispatch [:load-collectible-success strikers {id (http/parse-payload o)}]))
:on-error (fn [o]
(re-frame/dispatch [:load-collectible-failure strikers {id (http/parse-payload o)}]))}})
;;Etheremona
(def emona :EMONA)
(defmethod load-collectible-fx emona [_ _ id]
{:http-get {:url (str "https://www.etheremon.com/api/monster/get_data?monster_ids=" id)
:success-event-creator (fn [o]
[:load-collectible-success emona (:data (http/parse-payload o))])
:failure-event-creator (fn [o]
[:load-collectible-failure emona {id (http/parse-payload o)}])}})
{:http-get {:url (str "https://www.etheremon.com/api/monster/get_data?monster_ids=" id)
:on-success (fn [o]
(re-frame/dispatch [:load-collectible-success emona (:data (http/parse-payload o))]))
:on-error (fn [o]
(re-frame/dispatch [:load-collectible-failure emona {id (http/parse-payload o)}]))}})
;;Kudos
(def kudos :KDO)
@ -115,11 +115,11 @@
(defmethod load-collectible-fx superrare [_ _ ids]
{:http-get-n (mapv (fn [id]
{:url id
:success-event-creator (fn [o]
[:load-collectible-success superrare {id (http/parse-payload o)}])
:failure-event-creator (fn [o]
[:load-collectible-failure superrare {id (http/parse-payload o)}])})
{:url id
:on-success (fn [o]
(re-frame/dispatch [:load-collectible-success superrare {id (http/parse-payload o)}]))
:on-error (fn [o]
(re-frame/dispatch [:load-collectible-failure superrare {id (http/parse-payload o)}]))})
ids)})
(def graphql-url "https://api.pixura.io/graphql")
@ -137,31 +137,33 @@
}}}}"))
(defmethod load-collectibles-fx superrare [_ _ _ address _]
{:http-post {:url graphql-url
:data (types/clj->json {:query (graphql-query (ethereum/naked-address address))})
:opts {:headers {"Content-Type" "application/json"}}
:success-event-creator (fn [{:keys [response-body]}]
[:store-collectibles superrare
(get-in (http/parse-payload response-body) [:data :collectiblesByOwner :collectibles])])
:failure-event-creator (fn [{:keys [response-body]}]
[:load-collectibles-failure (http/parse-payload response-body)])
:timeout-ms 10000}})
{:http-post {:url graphql-url
:data (types/clj->json {:query (graphql-query (ethereum/naked-address address))})
:opts {:headers {"Content-Type" "application/json"}}
:on-success (fn [{:keys [response-body]}]
(re-frame/dispatch [:store-collectibles superrare
(get-in (http/parse-payload response-body)
[:data :collectiblesByOwner :collectibles])]))
:on-error (fn [{:keys [response-body]}]
(re-frame/dispatch [:load-collectibles-failure (http/parse-payload response-body)]))
:timeout-ms 10000}})
(handlers/register-handler-fx
:token-uri-success
(fn [_ [_ tokenId token-uri]]
{:http-get {:url
token-uri
:success-event-creator
:on-success
(fn [o]
[:load-collectible-success kudos {tokenId (update (http/parse-payload o)
:image
string/replace
#"http:"
"https:")}]) ;; http in mainnet
:failure-event-creator
(re-frame/dispatch [:load-collectible-success kudos
{tokenId (update (http/parse-payload o)
:image
string/replace
#"http:"
"https:")}])) ;; http in mainnet
:on-error
(fn [o]
[:load-collectible-failure kudos {tokenId (http/parse-payload o)}])}}))
(re-frame/dispatch [:load-collectible-failure kudos {tokenId (http/parse-payload o)}]))}}))
(handlers/register-handler-fx
:load-collectible

View File

@ -551,13 +551,17 @@
"invalid-number": "Invalid number",
"invalid-pairing-password": "Invalid pairing password",
"invalid-range": "Invalid format, must be between {{min}} and {{max}}",
"join-me": "Hey join me on Status: {{url}}",
"http-gateway-error": "Oops, request failed!",
"sign-request-failed": "Could not sign message",
"invite-friends": "Invite friends",
"invite-reward": "Earn {{value}} for every friend you invite!",
"invite-reward": "Earn crypto for every friend you invite!",
"invite-select-account": "Select an account to receive your referral bonus",
"invited": "invited",
"invite-button": "Invite",
"invite-receive-account": "Account to receive your referral bonus",
"invite-instruction": "How it works",
"invite-warning": "This promotion is only valid for users of an Android device, who aren't residents of US. Friend needs to confirm referral within 7 days",
"invite-instruction-first": "You send a unique invite link to your friend to download and join Status",
"invite-instruction-second": "Your friend downloads Status and creates an account (on Android)",
"invite-instruction-third": "A chat with your friend is started, where they confirm your referral",
@ -575,11 +579,19 @@
"invite-chat-intro": "You were referred by a friend to join Status. Heres some crypto to get you started! Use it to register an ENS name or buy a sticker pack",
"invite-chat-accept": "Accept",
"invite-chat-rule": "Accepting will also reward your friend with a crypto referral bonus",
"redeem-now": "Redeem now",
"redeem-amount": "{{quantity}} bonuses available",
"redeem-success": "Redeem bonus success!",
"attribution-received": "{{attrib}} out of {{max}} bonuses received",
"advertiser-starter-pack-title": "Starter Pack",
"advertiser-starter-pack-description": "Heres some crypto to get you started! Use it to get stickers, an ENS name and try dapps",
"advertiser-title": "Privacy by default",
"advertiser-description": "Youve discovered Status thanks to a partner. Do you mind if Status checks your IP address once so they get rewarded? This information will not be used for anything else and it will be removed completely after 7 days.",
"advertiser-starter-pack-accept": "Accept",
"advertiser-starter-pack-decline": "Decline",
"dapp-starter-pack-title": "Starter Pack",
"dapp-starter-pack-description": "Heres some crypto to get you started! Use it to get stickers, an ENS name and try dapps",
"dapp-starter-pack-accept": "Accept and Open",
"starter-pack-received": "Starter Pack received",
"starter-pack-received-description": "Heres some crypto to get you started! Use it to get stickers, an ENS name and try dapps",
"join-group-chat": "Join group",

View File

@ -569,6 +569,12 @@
"intro-wizard-title5": "Confirma la clave de acceso",
"intro-wizard-title6": "Habilitar las notificaciones",
"invalid-address-qr-code": "El código QR escaneado no contiene una dirección válida",
"advertiser-title": "Privacidad por defecto",
"advertiser-description": "Descubriste Status gracias a un socio nuestro. Podemos chequear tu IP para poder recompensarlo? Esta información no será usada para nada más, y será removida completamente después de 7 días.",
"advertiser-starter-pack-accept": "Aceptar",
"advertiser-starter-pack-decline": "Rechazar",
"invite-privacy-policy1": "Al aceptar estas de acuerdo con los",
"invite-privacy-policy2": "Términos y Condiciones",
"invalid-format": "Formato inválido \n Debe ser {{format}}",
"invalid-key-confirm": "Aplicar",
"invalid-key-content": "La base de datos no se puede encriptar porque el archivo está dañado. Tus fondos y llaves de chat están a salvo. Otros datos, como chats y contactos, no se podrán restaurar. Presionar el botón {{erase-multiaccounts-data-button-text}}\" eliminará otros datos y te permitirá acceder a tus fondos y enviar mensajes",
@ -1210,4 +1216,4 @@
"your-keys": "Tus claves",
"your-recovery-phrase": "Tu frase semilla",
"your-recovery-phrase-description": "Esta es tu frase semilla. La usas para comprobar que esta es tu billetera. ¡Sólo la verás una vez! Escríbela en un papel y guárdala en un lugar seguro. La necesitarás si pierdes o reinstalas tu billetera."
}
}

View File

@ -569,6 +569,12 @@
"intro-wizard-title5": "Confirma la clave de acceso",
"intro-wizard-title6": "Habilitar las notificaciones",
"invalid-address-qr-code": "El código QR escaneado no contiene una dirección válida",
"advertiser-title": "Privacidad por defecto",
"advertiser-description": "Descubriste Status gracias a un socio nuestro. Podemos chequear tu IP para poder recompensarlo? Esta información no será usada para nada más, y será removida completamente después de 7 días.",
"advertiser-starter-pack-accept": "Aceptar",
"advertiser-starter-pack-decline": "Rechazar",
"invite-privacy-policy1": "Al aceptar estas de acuerdo con los",
"invite-privacy-policy2": "Términos y Condiciones",
"invalid-format": "Formato inválido \n Debe ser {{format}}",
"invalid-key-confirm": "Aplicar",
"invalid-key-content": "La base de datos no se puede encriptar porque el archivo está dañado. Tus fondos y llaves de chat están a salvo. Otros datos, como chats y contactos, no se podrán restaurar. Presionar el botón {{erase-multiaccounts-data-button-text}}\" eliminará otros datos y te permitirá acceder a tus fondos y enviar mensajes",
@ -1210,4 +1216,4 @@
"your-keys": "Tus claves",
"your-recovery-phrase": "Tu frase semilla",
"your-recovery-phrase-description": "Esta es tu frase semilla. La usas para comprobar que esta es tu billetera. ¡Sólo la verás una vez! Escríbela en un papel y guárdala en un lugar seguro. La necesitarás si pierdes o reinstalas tu billetera."
}
}

View File

@ -573,6 +573,12 @@
"intro-wizard-title5": "Confirme a senha",
"intro-wizard-title6": "Ativar notificações",
"invalid-address-qr-code": "O código QR digitalizado não contém um endereço válido",
"advertiser-title": "Privacidade é importante",
"advertiser-description": "Você chegou até a Status graças a um parceiro nosso. Você se importaria se o app verificasse seu endereço IP, apenas uma vez, para podemos contabilizar isto? Esta informação não será utilizada para nada mais, e será completamente apagada após 7 dias.",
"advertiser-starter-pack-accept": "Aceitar",
"advertiser-starter-pack-decline": "Rejeitar",
"invite-privacy-policy1": "Ao aceitar você concorda com os",
"invite-privacy-policy2": "Termos e Condições",
"invalid-format": "Formato Inválido \n Deve ser {{format}}",
"invalid-key-confirm": "Aplicar",
"invalid-key-content": "O banco de dados não pode ser criptografado porque um arquivo está corrompido. Seus fundos e a chave de bate-papo estão seguros. Outros dados, como seus chats e contatos, não podem ser restaurados. O botão \"{{erase-multiaccounts-data-button-text}}\" removerá todos os outros dados e permitirá que você acesse seus fundos e envie mensagens",
@ -1215,4 +1221,4 @@
"your-keys": "Suas chaves",
"your-recovery-phrase": "Sua frase-semente",
"your-recovery-phrase-description": "Esta é sua frase-semente. Você o usa para provar que esta é sua carteira. Você só pode vê-lo uma vez! Escreva no papel e mantenha em um lugar seguro. Você vai precisar dele se você perder ou reinstalar sua carteira."
}
}

View File

@ -612,12 +612,12 @@
"invite-privacy-policy1": "Принимая, вы соглашаетесь с реферальной программой",
"invite-privacy-policy2": "Правила и условия.",
"invite-receive-account": "Учетная запись для получения вашего реферального бонуса",
"invite-reward": "Зарабатывайте {{value}} за каждого приглашенного друга!",
"invite-reward": "Зарабатывайте за каждого приглашенного друга!",
"invite-reward-friend": "Друг: ",
"invite-reward-friend-description": "Ваш друг получит стартовый набор, состоящий из некоторых {{вознаграждений}} для начала.",
"invite-reward-friend-description": "Ваш друг получит стартовый набор, состоящий из некоторых {{reward}} для начала.",
"invite-reward-friend-name": "Стартовый набор",
"invite-reward-you": "Вы",
"invite-reward-you-description": "Пригласите друга и получите {{вознаграждение}} в знак нашей благодарности.",
"invite-reward-you": "Вы: ",
"invite-reward-you-description": "Пригласите друга и получите {{reward}} в знак нашей благодарности.",
"invite-reward-you-name": "Реферальный бонус",
"invite-select-account": "Выберите учетную запись для получения вашего реферального бонуса",
"invited": "приглашен(а)",
@ -1242,4 +1242,4 @@
"your-keys": "Ваши ключи",
"your-recovery-phrase": "Ваша фраза восстановления",
"your-recovery-phrase-description": "Это ваша фраза восстановления. Она нужна для того, чтобы доказать, что это ваш кошелек. Вы можете ее увидеть только один раз! Запишите ее на бумаге и храните в надежном месте. Она понадобится вам, если вы потеряете или переустановите свой кошелек."
}
}