Implement Token Gating (#13899)

* feat: implement token gating component

* fix: add .calva to gitignore
This commit is contained in:
Christoph Pader 2022-12-01 14:30:07 +01:00 committed by GitHub
parent 5947769881
commit f18044c9dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 767 additions and 204 deletions

2
.gitignore vendored
View File

@ -179,3 +179,5 @@ test/appium/tests/users.py
## visual tests ## visual tests
/artifacts /artifacts
/.calva/

View File

@ -7,147 +7,149 @@
[quo2.components.icon :as quo2.icons])) [quo2.components.icon :as quo2.icons]))
(def themes (def themes
{:light {:primary {:icon-color colors/white {:light {:primary {:icon-color colors/white
:label {:style {:color colors/white}} :label-color colors/white
:background-color {:default colors/primary-50 :background-color {:default colors/primary-50
:pressed colors/primary-60 :pressed colors/primary-60
:disabled colors/primary-50}} :disabled colors/primary-50}}
:secondary {:icon-color colors/primary-50 :secondary {:icon-color colors/primary-50
:label {:style {:color colors/primary-50}} :label-color colors/primary-50
:background-color {:default colors/primary-50-opa-20 :background-color {:default colors/primary-50-opa-20
:pressed colors/primary-50-opa-40 :pressed colors/primary-50-opa-40
:disabled colors/primary-50-opa-20}} :disabled colors/primary-50-opa-20}}
:grey {:icon-color colors/neutral-100 :grey {:icon-color colors/neutral-100
:icon-secondary-color colors/neutral-50 :icon-secondary-color colors/neutral-50
:label {:style {:color colors/neutral-100}} :label-color colors/neutral-100
:background-color {:default colors/neutral-10 :background-color {:default colors/neutral-10
:pressed colors/neutral-20 :pressed colors/neutral-20
:disabled colors/neutral-10}} :disabled colors/neutral-10}}
:dark-grey {:icon-color colors/neutral-100 :dark-grey {:icon-color colors/neutral-100
:icon-secondary-color colors/neutral-50 :icon-secondary-color colors/neutral-50
:label {:style {:color colors/neutral-100}} :label-color colors/neutral-100
:background-color {:default colors/neutral-20 :background-color {:default colors/neutral-20
:pressed colors/neutral-30 :pressed colors/neutral-30
:disabled colors/neutral-20}} :disabled colors/neutral-20}}
:outline {:icon-color colors/neutral-50 :outline {:icon-color colors/neutral-50
:icon-secondary-color colors/neutral-50 :icon-secondary-color colors/neutral-50
:label {:style {:color colors/neutral-100}} :label-color colors/neutral-100
:border-color {:default colors/neutral-20 :border-color {:default colors/neutral-20
:pressed colors/neutral-40 :pressed colors/neutral-40
:disabled colors/neutral-20}} :disabled colors/neutral-20}}
:ghost {:icon-color colors/neutral-50 :ghost {:icon-color colors/neutral-50
:icon-secondary-color colors/neutral-50 :icon-secondary-color colors/neutral-50
:label {:style {:color colors/neutral-100}} :label-color colors/neutral-100
:background-color {:pressed colors/neutral-10}} :background-color {:pressed colors/neutral-10}}
:danger {:icon-color colors/white :danger {:icon-color colors/white
:label {:style {:color colors/white}} :label-color colors/white
:background-color {:default colors/danger-50 :background-color {:default colors/danger-50
:pressed colors/danger-60 :pressed colors/danger-60
:disabled colors/danger-50}} :disabled colors/danger-50}}
:positive {:icon-color colors/white :positive {:icon-color colors/white
:label {:style {:color colors/white}} :label-color colors/white
:background-color {:default colors/success-50 :background-color {:default colors/success-50
:pressed colors/success-60 :pressed colors/success-60
:disabled colors/success-50-opa-30}} :disabled colors/success-50-opa-30}}
:photo-bg {:icon-color colors/neutral-100 :photo-bg {:icon-color colors/neutral-100
:icon-secondary-color colors/neutral-80-opa-40 :icon-secondary-color colors/neutral-80-opa-40
:label {:style {:color colors/neutral-100}} :label-color colors/neutral-100
:background-color {:default colors/white-opa-40 :background-color {:default colors/white-opa-40
:pressed colors/white-opa-50 :pressed colors/white-opa-50
:disabled colors/white-opa-40}} :disabled colors/white-opa-40}}
:blur-bg {:icon-color colors/neutral-100 :blur-bg {:icon-color colors/neutral-100
:icon-secondary-color colors/neutral-80-opa-40 :icon-secondary-color colors/neutral-80-opa-40
:label {:style {:color colors/neutral-100}} :label-color colors/neutral-100
:background-color {:default colors/neutral-80-opa-5 :background-color {:default colors/neutral-80-opa-5
:pressed colors/neutral-80-opa-10 :pressed colors/neutral-80-opa-10
:disabled colors/neutral-80-opa-5}} :disabled colors/neutral-80-opa-5}}
:blur-bg-outline {:icon-color colors/neutral-100 :blur-bg-outline {:icon-color colors/neutral-100
:icon-secondary-color colors/neutral-80-opa-40 :icon-secondary-color colors/neutral-80-opa-40
:label {:style {:color colors/neutral-100}} :label-color colors/neutral-100
:border-color {:default colors/neutral-80-opa-10 :border-color {:default colors/neutral-80-opa-10
:pressed colors/neutral-80-opa-20 :pressed colors/neutral-80-opa-20
:disabled colors/neutral-80-opa-10}} :disabled colors/neutral-80-opa-10}}
:shell {:icon-color colors/white :shell {:icon-color colors/white
:label {:style {:color colors/white}} :label-color colors/white
:background-color {:default colors/neutral-95 :background-color {:default colors/neutral-95
:pressed colors/neutral-95 :pressed colors/neutral-95
:disabled colors/neutral-95}}} :disabled colors/neutral-95}}}
:dark {:primary {:icon-color colors/white :dark {:primary {:icon-color colors/white
:label {:style {:color colors/white}} :label-color colors/white
:background-color {:default colors/primary-60 :background-color {:default colors/primary-60
:pressed colors/primary-50 :pressed colors/primary-50
:disabled colors/primary-60}} :disabled colors/primary-60}}
:secondary {:icon-color colors/primary-50 :secondary {:icon-color colors/primary-50
:label {:style {:color colors/primary-50}} :label-color colors/primary-50
:background-color {:default colors/primary-50-opa-20 :background-color {:default colors/primary-50-opa-20
:pressed colors/primary-50-opa-30 :pressed colors/primary-50-opa-30
:disabled colors/primary-50-opa-20}} :disabled colors/primary-50-opa-20}}
:grey {:icon-color colors/white :grey {:icon-color colors/white
:icon-secondary-color colors/neutral-40 :icon-secondary-color colors/neutral-40
:label {:style {:color colors/white}} :label-color colors/white
:background-color {:default colors/neutral-80 :background-color {:default colors/neutral-80
:pressed colors/neutral-60 :pressed colors/neutral-60
:disabled colors/neutral-80}} :disabled colors/neutral-80}}
:dark-grey {:icon-color colors/white :dark-grey {:icon-color colors/white
:icon-secondary-color colors/neutral-40 :icon-secondary-color colors/neutral-40
:label {:style {:color colors/white}} :label-color colors/white
:background-color {:default colors/neutral-70 :background-color {:default colors/neutral-70
:pressed colors/neutral-60 :pressed colors/neutral-60
:disabled colors/neutral-70}} :disabled colors/neutral-70}}
:outline {:icon-color colors/neutral-40 :outline {:icon-color colors/neutral-40
:icon-secondary-color colors/neutral-40 :icon-secondary-color colors/neutral-40
:label {:style {:color colors/white}} :label-color colors/white
:border-color {:default colors/neutral-70 :border-color {:default colors/neutral-70
:pressed colors/neutral-60 :pressed colors/neutral-60
:disabled colors/neutral-70}} :disabled colors/neutral-70}}
:ghost {:icon-color colors/neutral-40 :ghost {:icon-color colors/neutral-40
:icon-secondary-color colors/neutral-40 :icon-secondary-color colors/neutral-40
:label {:style {:color colors/white}} :label-color colors/white
:background-color {:pressed colors/neutral-80}} :background-color {:pressed colors/neutral-80}}
:danger {:icon-color colors/white :danger {:icon-color colors/white
:label {:style {:color colors/white}} :label-color colors/white
:background-color {:default colors/danger-60 :background-color {:default colors/danger-60
:pressed colors/danger-50 :pressed colors/danger-50
:disabled colors/danger-60}} :disabled colors/danger-60}}
:positive {:icon-color colors/white :positive {:icon-color colors/white
:label {:style {:color colors/white}} :label-color colors/white
:background-color {:default colors/success-60 :background-color {:default colors/success-60
:pressed colors/success-50 :pressed colors/success-50
:disabled colors/success-60-opa-30}} :disabled colors/success-60-opa-30}}
:photo-bg {:icon-color colors/white :photo-bg {:icon-color colors/white
:icon-secondary-color colors/neutral-30 :icon-secondary-color colors/neutral-30
:label {:style {:color colors/white}} :label-color colors/white
:background-color {:default colors/neutral-80-opa-40 :background-color {:default colors/neutral-80-opa-40
:pressed colors/neutral-80-opa-50 :pressed colors/neutral-80-opa-50
:disabled colors/neutral-80-opa-40}} :disabled colors/neutral-80-opa-40}}
:blur-bg {:icon-color colors/white :blur-bg {:icon-color colors/white
:icon-secondary-color colors/white-opa-40 :icon-secondary-color colors/white-opa-40
:label {:style {:color colors/white}} :label-color colors/white
:background-color {:default colors/white-opa-5 :background-color {:default colors/white-opa-5
:pressed colors/white-opa-10 :pressed colors/white-opa-10
:disabled colors/white-opa-5}} :disabled colors/white-opa-5}}
:blur-bg-outline {:icon-color colors/white :blur-bg-outline {:icon-color colors/white
:icon-secondary-color colors/white-opa-40 :icon-secondary-color colors/white-opa-40
:label {:style {:color colors/white}} :label-color colors/white
:border-color {:default colors/white-opa-10 :border-color {:default colors/white-opa-10
:pressed colors/white-opa-20 :pressed colors/white-opa-20
:disabled colors/white-opa-5}} :disabled colors/white-opa-5}}
:shell {:icon-color colors/white :shell {:icon-color colors/white
:label {:style {:color colors/white}} :label-color colors/white
:background-color {:default colors/neutral-95}}}}) :background-color {:default colors/neutral-95}}}})
(defn shape-style-container [type icon size]
{:border-radius (if (and icon (#{:primary :secondary :danger} type))
24
(case size
56 12
40 12
32 10
24 8))})
(defn style-container [type size disabled background-color border-color icon above width before after] (defn style-container [type size disabled background-color border-color icon above width before after]
(merge {:height size (merge {:height size
:align-items :center :align-items :center
:justify-content :center :justify-content :center
:flex-direction (if above :column :row) :flex-direction (if above :column :row)
:border-radius (if (and icon (#{:primary :secondary :danger} type))
24
(case size
56 12
40 12
32 10
24 8))
:background-color background-color :background-color background-color
:padding-horizontal (when-not (or icon before after) :padding-horizontal (when-not (or icon before after)
(case size 56 16 40 16 32 12 24 8)) (case size 56 16 40 16 32 12 24 8))
@ -159,6 +161,7 @@
(case size 56 0 40 9 32 5 24 3)) (case size 56 0 40 9 32 5 24 3))
:padding-bottom (when-not (or icon before after) :padding-bottom (when-not (or icon before after)
(case size 56 0 40 9 32 5 24 4))} (case size 56 0 40 9 32 5 24 4))}
(shape-style-container type icon size)
(when width (when width
{:width width}) {:width width})
(when icon (when icon
@ -169,14 +172,19 @@
(when disabled (when disabled
{:opacity 0.3}))) {:opacity 0.3})))
(defn community-themed? [type community-color]
(and (= type :community) (string? community-color)))
(defn button (defn button
"with label "with label
[button opts \"label\"] [button opts \"label\"]
opts opts
{:type :primary/:secondary/:grey/:dark-grey/:outline/:ghost/ {:type :primary/:secondary/:grey/:dark-grey/:outline/:ghost/
:danger/:photo-bg/:blur-bg/:blur-bg-ouline/:shell :danger/:photo-bg/:blur-bg/:blur-bg-ouline/:shell/:community
:size 40/32/24 :size 40/32/24
:icon true/false :icon true/false
:community-color '#FFFFFF'
:community-text-color '#000000'
:before :icon-keyword :before :icon-keyword
:after :icon-keyword} :after :icon-keyword}
@ -184,13 +192,13 @@
[button {:icon true} :main-icons/close-circle]" [button {:icon true} :main-icons/close-circle]"
[_ _] [_ _]
(let [pressed (reagent/atom false)] (let [pressed (reagent/atom false)]
(fn [{:keys [on-press disabled type size before after above width (fn [{:keys [on-press disabled type size community-color community-text-color before after above width
override-theme override-background-color override-theme override-background-color
on-long-press accessibility-label icon icon-no-color style test-ID] on-long-press accessibility-label icon icon-no-color style test-ID]
:or {type :primary :or {type :primary
size 40}} size 40}}
children] children]
(let [{:keys [icon-color icon-secondary-color background-color label border-color]} (let [{:keys [icon-color icon-secondary-color background-color label-color border-color]}
(get-in themes [(or (get-in themes [(or
override-theme override-theme
(theme/get-theme)) type]) (theme/get-theme)) type])
@ -212,48 +220,65 @@
(reset! pressed nil))}) (reset! pressed nil))})
[rn/view {:style (merge [rn/view {:style (merge
(style-container (shape-style-container type icon size)
type {:background-color
size (if (= state :pressed)
disabled (colors/theme-colors colors/neutral-100 colors/white)
(or override-background-color (get background-color state)) :transparent)}
(get border-color state)
icon
above
width
before
after)
style)} style)}
(when above [rn/view {:style (merge
[rn/view (style-container
[quo2.icons/icon above {:container-style {:margin-bottom 2} type
:color icon-secondary-color size
:size icon-size}]]) disabled
(when before (or override-background-color (get background-color state))
[rn/view (get border-color state)
[quo2.icons/icon before {:container-style {:margin-left (if (= size 40) 12 8) icon
:margin-right 4} above
width
before
after)
(when
(community-themed? type community-color)
(merge
{:background-color community-color}
(when (= state :pressed) {:opacity 0.9}))))}
(when above
[rn/view
[quo2.icons/icon above {:container-style {:margin-bottom 2}
:color icon-secondary-color :color icon-secondary-color
:size icon-size}]]) :size icon-size}]])
[rn/view (when before
(cond [rn/view
(or icon icon-no-color) [quo2.icons/icon before {:container-style {:margin-left (if (= size 40) 12 8)
[quo2.icons/icon children {:color icon-color :margin-right 4}
:no-color icon-no-color :color icon-secondary-color
:size icon-size}] :size icon-size}]])
[rn/view
(cond
(or icon icon-no-color)
[quo2.icons/icon children {:color icon-color
:no-color icon-no-color
:size icon-size}]
(string? children) (string? children)
[text/text (merge {:size (when (#{56 24} size) :paragraph-2) [text/text {:size (when (#{56 24} size) :paragraph-2)
:weight :medium :weight :medium
:number-of-lines 1} :number-of-lines 1
label) :style {:color (if
children] (and
(community-themed? type community-color)
(string? community-text-color))
community-text-color
label-color)}}
(vector? children) children]
children)]
(when after (vector? children)
[rn/view children)]
[quo2.icons/icon after {:container-style {:margin-left 4 (when after
:margin-right (if (= size 40) 12 8)} [rn/view
:color icon-secondary-color [quo2.icons/icon after {:container-style {:margin-left 4
:size icon-size}]])]])))) :margin-right (if (= size 40) 12 8)}
:color icon-secondary-color
:size icon-size}]])]]]))))

View File

@ -69,10 +69,12 @@
:style {:margin-top (if (= size :large) 8 2)}} :style {:margin-top (if (= size :large) 8 2)}}
description])]) description])])
(defn permission-tag-container [{:keys [locked? tokens]}] (defn permission-tag-container [{:keys [locked? tokens on-press]}]
[permission/tag {:background-color (colors/theme-colors [permission/tag {:background-color (colors/theme-colors
colors/neutral-10 colors/neutral-10
colors/neutral-80) colors/neutral-80)
:locked? locked? :locked? locked?
:tokens tokens :tokens tokens
:size 24}])
:size 24
:on-press on-press}])

View File

@ -0,0 +1,259 @@
(ns quo2.components.community.token-gating
(:require [quo.react-native :as rn]
[quo2.components.avatars.channel-avatar :as channel-avatar]
[quo2.components.buttons.button :as button]
[quo2.components.icon :as icon]
[quo2.components.info.information-box :as information-box]
[quo2.components.markdown.text :as text]
[quo2.components.tags.token-tag :as token-tag]
[quo2.foundations.colors :as colors]
[status-im.i18n.i18n :as i18n]
[status-im.ui.components.fast-image :as fast-image]))
(def ^:private token-tag-horizontal-spacing 7)
(def token-tag-vertical-spacing 5)
(def styles {:container {:flex 1
:padding-horizontal 20}
:header-container {:flex-direction :row
:align-items :center}
:header-spacing-community {:margin-bottom 16}
:header-spacing-channel {:margin-bottom 24}
:header-avatar {:margin-right 8}
:header-community-avatar-image {:width 32 :height 32}
:header-title-container {:flex 1
:flex-direction :row
:align-items :center}
:header-title-lock {:margin-left 6}
:header-info-button {:margin-left 8}
:token-requirement-text-spacing {:margin-vertical 8}
:token-requirement-list-spacing {:padding-vertical (- 8 token-tag-vertical-spacing)}
:token-row {:flex-direction :row
:flex-wrap :wrap
:align-items :center}
:token-row-container {:flex-direction :row
:align-items :center
:margin-horizontal (- token-tag-horizontal-spacing)}
:token-row-container-no-shift {:margin-left 0}
:token-row-or-text {:margin-right 3}
:token-tag-spacing {:margin-horizontal token-tag-horizontal-spacing
:margin-vertical token-tag-vertical-spacing}
:divider {:height 1
:margin-horizontal -20
:margin-top (- 12 token-tag-vertical-spacing)
:margin-bottom 8}
:membership-request-denied {:margin-top 13}
:enter-button {:margin-top 16}
:info-text {:margin-top 12
:text-align :center
:padding-horizontal 20}})
(defn multiple-token-requirements? [token-requirement-lists]
(when (vector? (first token-requirement-lists))
(> (count token-requirement-lists) 1)))
(defn is-token-requirement-met? [token-requirement-list]
(and (seq token-requirement-list)
(every?
(fn [token]
(get token :is-sufficient?))
token-requirement-list)))
(defn are-multiple-token-requirements-met? [token-requirement-lists]
(if (multiple-token-requirements? token-requirement-lists)
(some is-token-requirement-met? token-requirement-lists)
(is-token-requirement-met? token-requirement-lists)))
(defn token-requirement-list-row [tokens community-color]
[rn/view {:style (merge
(get styles :token-row)
(get styles :token-requirement-list-spacing))}
(map-indexed (fn [token-index token]
(let [{:keys [token-img-src token amount is-sufficient? is-purchasable?]} token]
^{:key token-index}
[rn/view {:style (get styles :token-tag-spacing)}
[token-tag/token-tag {:token token
:value amount
:size 24
:border-color community-color
:is-required is-sufficient?
:is-purchasable is-purchasable?
:token-img-src token-img-src}]]))
tokens)])
(defn token-requirement-list [props community-color]
(let [{:keys [gate token-requirements-changed? required-tokens-lost?]} props
[gate-type token-requirement-lists] gate
multiple-token-requirements? (multiple-token-requirements? token-requirement-lists)
is-sufficient? (are-multiple-token-requirements-met? token-requirement-lists)
you-must-hold-label (if (= gate-type :join)
(cond
token-requirements-changed? :t/you-must-now-hold
required-tokens-lost? :t/you-must-always-hold
:else :t/you-must-hold)
:t/you-must-hold)
message-label (cond
(= gate-type :join)
(cond
token-requirements-changed? :t/community-join-requirements-changed
required-tokens-lost? :t/community-join-requirements-tokens-lost
:else (if is-sufficient? :t/community-join-requirements-met :t/community-join-requirements-not-met))
(= gate-type :read)
(if is-sufficient?
:t/community-channel-read-requirements-met
:t/community-channel-read-requirements-not-met)
(= gate-type :write)
(if is-sufficient?
:t/community-channel-write-requirements-met
:t/community-channel-write-requirements-not-met))]
[rn/view
[rn/view {:style (get styles :token-requirement-text-spacing)}
[text/text {:weight :medium
:number-of-lines 1}
(i18n/label
message-label)]
[text/text {:size :paragraph-2
:number-of-lines 1}
(i18n/label
you-must-hold-label)]]
(if multiple-token-requirements?
(map-indexed
(fn [token-requirement-index tokens]
^{:key token-requirement-index}
[rn/view {:style (merge
(get styles :token-row-container)
(when-not (= token-requirement-index 0)
(get styles :token-row-container-no-shift)))}
(when-not (= token-requirement-index 0)
[text/text {:style (get styles :token-row-or-text)
:size :paragraph-2} "or"])
[token-requirement-list-row tokens community-color]])
token-requirement-lists)
[rn/view {:style (get styles :token-row-container)}
[token-requirement-list-row token-requirement-lists community-color]])]))
(defn is-community-locked? [community]
(not (are-multiple-token-requirements-met? (get-in community [:gates :join]))))
(defn is-channel-locked? [channel]
(not (are-multiple-token-requirements-met?
(or (get-in channel [:gates :read])
(get-in channel [:gates :write])))))
(defn token-gating
"[token-gating opts]
opts
{
:community {
:name string
:community-avatar-img-src string
:community-color string
:community-text-color string
:token-requirements-changed? boolean
:required-tokens-lost? boolean
}
:channel {
:name string
:emoji string
:emoji-background-color :string
:on-enter-channel callback
:membership-request-denied? boolean
}
}"
[_ _]
(fn [{:keys [community channel]}]
(let [type (if (some? community) :community :channel)
{:keys [name
emoji
emoji-background-color
on-enter-channel
community-avatar-img-src
community-color
community-text-color
membership-request-denied?
token-requirements-changed?
required-tokens-lost?
gates]} (if (= type :community) community channel)
locked? (if
(= type :community)
(is-community-locked? community)
(is-channel-locked? channel))]
[rn/view {:style (merge (get styles :container) {:background-color (colors/theme-colors colors/white colors/neutral-90)})}
[rn/view {:style (merge
(get styles :header-container)
(if
(= type :community)
(get styles :header-spacing-community)
(get styles :header-spacing-channel)))}
[rn/view {:style (get styles :header-avatar)}
(if (= type :community)
[fast-image/fast-image {:source community-avatar-img-src
:style (get styles :header-community-avatar-image)}]
[channel-avatar/channel-avatar {:big? true
:locked? locked?
:emoji emoji
:emoji-background-color emoji-background-color}])]
[rn/view {:style (get styles :header-title-container)}
[text/text {:weight :semi-bold
:number-of-lines 1
:size :heading-1
:style (get styles :header-text)} (if (= type :community) name (str "# " name))]
(when (= type :community)
[icon/icon (if locked?
:main-icons2/locked
:main-icons2/unlocked)
{:container-style (get styles :header-title-lock)
:color (colors/theme-colors
colors/neutral-50
colors/neutral-40)}])]
[button/button {:type :outline
:size 32
:icon true
:style (get styles :header-info-button)} :main-icons2/info]]
(map-indexed (fn [gate-index gate]
^{:key gate-index}
[:<>
(when-not (= gate-index 0)
[rn/view {:style (merge
(get styles :divider)
{:background-color (colors/theme-colors
colors/neutral-10
colors/neutral-80)})}])
[token-requirement-list {:gate gate :token-requirements-changed? token-requirements-changed? :required-tokens-lost? required-tokens-lost?} community-color]])
gates)
(when (= type :channel)
(if membership-request-denied?
[information-box/information-box {:type :error
:icon :main-icons2/untrustworthy
:no-icon-color? true
:style (get styles :membership-request-denied)}
(i18n/label
:t/membership-request-denied)]
[:<>
[button/button {:type :community
:community-color community-color
:community-text-color community-text-color
:style (get styles :enter-button)
:disabled locked?
:on-press on-enter-channel}
(str "# " (i18n/label
:t/enter-channel))]
[text/text
{:size :paragraph-2
:style (merge
(get styles :info-text)
{:color (colors/theme-colors
colors/neutral-50
colors/neutral-40)})}
(i18n/label
:t/community-enter-channel-info)]]))])))

View File

@ -19,19 +19,21 @@
(defn info-message (defn info-message
"[info-message opts \"message\"] "[info-message opts \"message\"]
opts opts
{:type :default/:success/:error {:type :default/:success/:error
:size :default/:tiny :size :default/:tiny
:icon :main-icons/info ;; info message icon :icon :main-icons/info ;; info message icon
:text-color colors/white ;; text color override :text-color colors/white ;; text color override
:icon-color colors/white ;; icon color override" :icon-color colors/white ;; icon color override
[{:keys [type size icon text-color icon-color]} message] :no-icon-color? false ;; disable tint color for icon"
[{:keys [type size icon text-color icon-color no-icon-color?]} message]
(let [weight (if (= size :default) :regular :medium) (let [weight (if (= size :default) :regular :medium)
size (if (= size :default) :paragraph-2 :label) size (if (= size :default) :paragraph-2 :label)
text-color (or text-color (get-color type)) text-color (or text-color (get-color type))
icon-color (or icon-color text-color)] icon-color (or icon-color text-color)]
[rn/view {:style {:flex-direction :row [rn/view {:style {:flex-direction :row
:flex 1}} :flex 1}}
[quo2.icons/icon icon {:color icon-color [quo2.icons/icon icon {:color icon-color
:no-color no-icon-color?
:size 12 :size 12
:container-style {:margin-top 3}}] :container-style {:margin-top 3}}]
[text/text {:size size [text/text {:size size

View File

@ -49,11 +49,12 @@
:closed? true/false ;; information box's state :closed? true/false ;; information box's state
:id :information-box-id ;; unique id (required for closable? information box) :id :information-box-id ;; unique id (required for closable? information box)
:icon :main-icons/info ;; information box icon :icon :main-icons/info ;; information box icon
:no-icon-color? false ;; disable tint color for icon
:style style :style style
:button-label \"PressMe\" ;; add action button with label :button-label \"PressMe\" ;; add action button with label
:on-button-press action ;; (required for information box with button-label) :on-button-press action ;; (required for information box with button-label)
:on-close on-close ;; (optional on-close call)" :on-close on-close ;; (optional on-close call)"
[{:keys [type closable? closed? id icon style button-label on-button-press on-close]} message] [{:keys [type closable? closed? id icon style button-label on-button-press on-close no-icon-color?]} message]
(let [background-color (get-color-by-type type :bg) (let [background-color (get-color-by-type type :bg)
border-color (get-color-by-type type :border) border-color (get-color-by-type type :border)
icon-color (get-color-by-type type :icon) icon-color (get-color-by-type type :icon)
@ -73,7 +74,8 @@
[info-message/info-message {:size :default [info-message/info-message {:size :default
:icon icon :icon icon
:text-color text-color :text-color text-color
:icon-color icon-color} message] :icon-color icon-color
:no-icon-color? no-icon-color?} message]
(when closable? (when closable?
[rn/touchable-opacity [rn/touchable-opacity
{:on-press on-close {:on-press on-close

View File

@ -1,14 +1,14 @@
(ns quo2.components.list-items.channel (ns quo2.components.list-items.channel
(:require [react-native.core :as rn] (:require [quo2.components.avatars.channel-avatar :as channel-avatar]
[quo2.foundations.colors :as colors]
[quo2.components.counter.counter :as quo2.counter] [quo2.components.counter.counter :as quo2.counter]
[quo2.components.icon :as quo2.icons] [quo2.components.icon :as quo2.icons]
[quo2.components.avatars.channel-avatar :as channel-avatar]
[quo2.components.markdown.text :as quo2.text] [quo2.components.markdown.text :as quo2.text]
[quo2.theme :as theme])) [quo2.foundations.colors :as colors]
[quo2.theme :as theme]
[react-native.core :as rn]))
(defn list-item [{:keys [name locked? mentions-count unread-messages? (defn list-item [{:keys [name locked? mentions-count unread-messages?
muted? is-active-channel? emoji channel-color] muted? is-active-channel? emoji channel-color on-press]
:or {channel-color colors/primary-50}}] :or {channel-color colors/primary-50}}]
[rn/view {:style (merge {:height 48 [rn/view {:style (merge {:height 48
:display :flex :display :flex
@ -20,7 +20,8 @@
:padding-left 12 :padding-left 12
:padding-right 12} :padding-right 12}
(when is-active-channel? (when is-active-channel?
{:background-color (colors/theme-alpha channel-color 0.05 0.05)}))} {:background-color (colors/theme-alpha channel-color 0.05 0.05)}))
:on-press on-press}
[rn/view {:display :flex [rn/view {:display :flex
:flex-direction :row :flex-direction :row
:justify-content :flex-start :justify-content :flex-start

View File

@ -104,11 +104,12 @@
(defn tag (defn tag
[_ _] [_ _]
(fn [{:keys [locked tokens size background-color] (fn [{:keys [locked tokens size background-color on-press]
:or {size 24}}] :or {size 24}}]
[base-tag/base-tag {:background-color background-color [base-tag/base-tag {:background-color background-color
:size size :size size
:type :permission} :type :permission
:on-press on-press}
[rn/view {:flex-direction :row [rn/view {:flex-direction :row
:align-items :center :align-items :center
:justify-content :flex-end} :justify-content :flex-end}

View File

@ -12,11 +12,19 @@
[reagent.core :as reagent] [reagent.core :as reagent]
[quo.platform :as platform] [quo.platform :as platform]
[status-im2.contexts.communities.requests.actions.view :as requests.actions] [status-im2.contexts.communities.requests.actions.view :as requests.actions]
[status-im2.contexts.communities.home.actions.view :as home.actions])) [status-im2.contexts.communities.home.actions.view :as home.actions]
[quo2.components.community.token-gating :as token-gating]
[status-im.constants :as constants]
[status-im.react-native.resources :as resources]
[status-im.utils.utils :as utils]))
(def knc-token-img (js/require "../resources/images/tokens/mainnet/KNC.png"))
(def mana-token-img (js/require "../resources/images/tokens/mainnet/MANA.png"))
(def rare-token-img (js/require "../resources/images/tokens/mainnet/RARE.png"))
(def eth-token-img (js/require "../resources/images/tokens/mainnet/ETH.png"))
(def dai-token-img (js/require "../resources/images/tokens/mainnet/DAI.png"))
;; Mocked list items ;; Mocked list items
(def user-list (def user-list
[{:full-name "Alicia K"} [{:full-name "Alicia K"}
{:full-name "Marcus C"} {:full-name "Marcus C"}
@ -32,16 +40,69 @@
:size :label} :size :label}
"Join Alicia, Marcus and 2 more"]]) ;; TODO remove mocked data and use from contacts list/communities members "Join Alicia, Marcus and 2 more"]]) ;; TODO remove mocked data and use from contacts list/communities members
(defn open-token-gating-mocked [name emoji channel-color]
#(rf/dispatch
[:bottom-sheet/show-sheet
{:content
(constantly [token-gating/token-gating
{:channel {:name name
:community-color (colors/custom-color :pink 50)
:emoji emoji
:emoji-background-color channel-color
:on-enter-channel (fn [] (utils/show-popup "Entered channel" "Wuhuu!! You successfully entered the channel :)"))
:gates {:read [{:token "KNC"
:token-img-src knc-token-img
:amount 200
:is-sufficient? true}
{:token "MANA"
:token-img-src mana-token-img
:amount 10
:is-sufficient? false
:is-purchasable true}
{:token "RARE"
:token-img-src rare-token-img
:amount 10
:is-sufficient? false}]
:write [{:token "KNC"
:token-img-src knc-token-img
:amount 200
:is-sufficient? true}
{:token "DAI"
:token-img-src dai-token-img
:amount 20
:is-purchasable true
:is-sufficient? false}
{:token "ETH"
:token-img-src eth-token-img
:amount 0.5
:is-sufficient? false}]}}}])
:content-height 210}]))
(def list-of-channels {:Welcome [{:name "welcome" (def list-of-channels {:Welcome [{:name "welcome"
:emoji "🤝"} :emoji "🤝"}
{:name "onboarding" {:name "onboarding"
:emoji "🍑"} :emoji "🍑"
:locked? true
:on-press #((open-token-gating-mocked
"onboarding"
"🍑"
(colors/custom-color :pink 50)))}
{:name "intro" {:name "intro"
:emoji "🦄"}] :emoji "🦄"
:locked? true
:on-press #((open-token-gating-mocked
"intro"
"🦄"
(colors/custom-color :pink 50)))}]
:General [{:name "general" :General [{:name "general"
:emoji "🐷"} :emoji "🐷"}
{:name "people-ops" {:name "people-ops"
:emoji "🌏"} :emoji "🌏"
:locked? true
:on-press #((open-token-gating-mocked
"onboarding"
"🌏"
(colors/custom-color :blue 50)))}
{:name "announcements" {:name "announcements"
:emoji "🎺"}] :emoji "🎺"}]
:Mobile [{:name "mobile" :Mobile [{:name "mobile"
@ -123,9 +184,10 @@
(max 0) (max 0)
(min (if platform/ios? 100 124))))) (min (if platform/ios? 100 124)))))
(defn community-card-page-view [{:keys [name description locked joined (defn community-card-page-view [{:keys [name id description locked joined
status tokens cover tags community-color] :as community}] status tokens cover images tags community-color] :as community}]
(let [community-icon (memoize (fn [] [communities.icon/community-icon-redesign community 24])) (let [community-icon (memoize (fn [] [communities.icon/community-icon-redesign community 24]))
thumbnail-image (get-in images [:thumbnail])
scroll-height (reagent/atom scroll-0) scroll-height (reagent/atom scroll-0)
channel-heights (reagent/atom []) channel-heights (reagent/atom [])
first-channel-height (reagent/atom 0)] first-channel-height (reagent/atom 0)]
@ -211,7 +273,32 @@
[quo/permission-tag-container [quo/permission-tag-container
{:locked locked {:locked locked
:status status :status status
:tokens tokens}]]) :tokens tokens
:on-press #(rf/dispatch
[:bottom-sheet/show-sheet
{:content
(constantly [token-gating/token-gating
{:community {:name name
:community-color colors/primary-50
:community-avatar (cond
(= id constants/status-community-id)
(resources/get-image :status-logo)
(seq thumbnail-image)
thumbnail-image)
:gates {:join [{:token "KNC"
:token-img-src knc-token-img
:amount 200
:is-sufficient? true}
{:token "MANA"
:token-img-src mana-token-img
:amount 10
:is-sufficient? false
:is-purchasable true}
{:token "RARE"
:token-img-src rare-token-img
:amount 10
:is-sufficient? false}]}}}])
:content-height 210}])}]])
(when joined (when joined
[rn/view {:position :absolute [rn/view {:position :absolute

View File

@ -0,0 +1,164 @@
(ns status-im2.contexts.quo-preview.community.token-gating
(:require [quo.previews.preview :as preview]
[quo.react-native :as rn]
[quo2.components.community.token-gating :as quo2]
[quo2.foundations.colors :as colors]
[reagent.core :as reagent]
[status-im.utils.utils :as utils]))
(def styles {:container-sandbox {:flex 1
:padding-vertical 20
:border-top-left-radius 20
:border-top-right-radius 20}})
(def descriptor [{:label "Type:"
:key :type
:type :select
:options [{:key :community
:value "Community"}
{:key :channel
:value "Channel"}]}
{:label "Tokens sufficient:"
:key :is-sufficient?
:type :boolean}
{:label "Many tokens:"
:key :many-tokens?
:type :boolean}
{:label "Membership request denied:"
:key :membership-request-denied?
:type :boolean}])
(def eth-token-img (js/require "../resources/images/tokens/mainnet/ETH.png"))
(def knc-token-img (js/require "../resources/images/tokens/mainnet/KNC.png"))
(def mana-token-img (js/require "../resources/images/tokens/mainnet/MANA.png"))
(def rare-token-img (js/require "../resources/images/tokens/mainnet/RARE.png"))
(def dai-token-img (js/require "../resources/images/tokens/mainnet/DAI.png"))
(def fxc-token-img (js/require "../resources/images/tokens/mainnet/FXC.png"))
(def usdt-token-img (js/require "../resources/images/tokens/mainnet/USDT.png"))
(def snt-token-img (js/require "../resources/images/tokens/mainnet/SNT.png"))
(defn join-gate-options-base [is-sufficient? many-tokens?]
(into
[{:token "KNC"
:token-img-src knc-token-img
:amount 200
:is-sufficient? true}
{:token "MANA"
:token-img-src mana-token-img
:amount 10
:is-sufficient? is-sufficient?
:is-purchasable? true}
{:token "RARE"
:token-img-src rare-token-img
:amount 10
:is-sufficient? is-sufficient?}]
(when many-tokens? [{:token "FXC"
:token-img-src fxc-token-img
:amount 20
:is-sufficient? true}
{:token "SNT"
:token-img-src snt-token-img
:amount 10000
:is-sufficient? is-sufficient?}])))
(defn write-gate-options-base [is-sufficient?]
[{:token "KNC"
:token-img-src knc-token-img
:amount 200
:is-sufficient? true}
{:token "DAI"
:token-img-src dai-token-img
:amount 20
:is-purchasable? true
:is-sufficient? is-sufficient?}
{:token "ETH"
:token-img-src eth-token-img
:amount 0.5
:is-sufficient? is-sufficient?}])
(defn get-mocked-props [props]
(let [{:keys [type is-sufficient? many-tokens? membership-request-denied?]} props]
(if (= type :community) {:community {:name "Ethereum"
:community-color "#14044d"
:community-avatar-img-src eth-token-img
:gates {:join (if
many-tokens?
[(join-gate-options-base is-sufficient? many-tokens?)
[{:token "FXC"
:token-img-src fxc-token-img
:amount 20
:is-sufficient? true}
{:token "USDT"
:token-img-src usdt-token-img
:amount 20
:is-sufficient? false}]]
(join-gate-options-base is-sufficient? many-tokens?))}}}
{:channel {:name "onboarding"
:community-color (colors/custom-color :pink 50)
:community-text-color colors/white
:emoji "🍑"
:emoji-background-color "#F38888"
:on-enter-channel #(utils/show-popup "Entered channel" "Wuhuu!! You successfully entered the channel :)")
:membership-request-denied? membership-request-denied?
:gates {:read (into [{:token "KNC"
:token-img-src knc-token-img
:amount 200
:is-sufficient? true}
{:token "MANA"
:token-img-src mana-token-img
:amount 10
:is-sufficient? is-sufficient?
:is-purchasable? true}
{:token "RARE"
:token-img-src rare-token-img
:amount 10
:is-sufficient? is-sufficient?}]
(when many-tokens? [{:token "FXC"
:token-img-src fxc-token-img
:amount 20
:is-sufficient? true}
{:token "SNT"
:token-img-src snt-token-img
:amount 10000
:is-sufficient? is-sufficient?}]))
:write (if
many-tokens?
[(write-gate-options-base is-sufficient?)
[{:token "FXC"
:token-img-src fxc-token-img
:amount 20
:is-sufficient? true}
{:token "MANA"
:token-img-src mana-token-img
:amount 10
:is-sufficient? is-sufficient?}
{:token "USDT"
:token-img-src usdt-token-img
:amount 20
:is-sufficient? false}]]
(write-gate-options-base is-sufficient?))}}})))
(def state (reagent/atom {:type :channel
:is-sufficient? false
:many-tokens? false
:membership-request-denied? false}))
(defn preview-token-gating []
(let [preview-props (get-mocked-props @state)]
[rn/view {:style {:background-color (colors/theme-colors
colors/neutral-10
colors/neutral-80)
:flex 1}}
[rn/view {:style {:flex 1}}
[rn/view {:style {:position :absolute
:left 0
:right 0
:top 0}}
[preview/customizer state descriptor]]]
[rn/view {:height (if (= (:type @state) :community) 280 495) :margin-top 20}
[rn/view {:style (merge
(get styles :container-sandbox)
{:background-color (colors/theme-colors
colors/white
colors/neutral-90)})}
[quo2/token-gating preview-props]]]]))

View File

@ -53,6 +53,7 @@
[status-im2.contexts.quo-preview.wallet.network-amount :as network-amount] [status-im2.contexts.quo-preview.wallet.network-amount :as network-amount]
[status-im2.contexts.quo-preview.navigation.page-nav :as page-nav] [status-im2.contexts.quo-preview.navigation.page-nav :as page-nav]
[status-im2.contexts.quo-preview.avatars.account-avatar :as account-avatar] [status-im2.contexts.quo-preview.avatars.account-avatar :as account-avatar]
[status-im2.contexts.quo-preview.community.token-gating :as token-gating]
[re-frame.core :as re-frame])) [re-frame.core :as re-frame]))
(def screens-categories (def screens-categories
@ -94,7 +95,10 @@
:component community-membership-list-view/preview-community-list-view} :component community-membership-list-view/preview-community-list-view}
{:name :discover-card {:name :discover-card
:insets {:top false} :insets {:top false}
:component discover-card/preview-discoverd-card}] :component discover-card/preview-discoverd-card}
{:name :token-gating
:insets {:top false}
:component token-gating/preview-token-gating}]
:counter [{:name :counter :counter [{:name :counter
:insets {:top false} :insets {:top false}
:component counter/preview-counter}] :component counter/preview-counter}]

View File

@ -175,6 +175,7 @@
"member-kick": "Kick member", "member-kick": "Kick member",
"member-ban": "Ban member", "member-ban": "Ban member",
"membership-requests": "Membership requests", "membership-requests": "Membership requests",
"membership-request-denied": "Membership request denied",
"community-members-title": "Members", "community-members-title": "Members",
"community-requests-to-join-title": "Membership requests", "community-requests-to-join-title": "Membership requests",
"name-your-channel": "Name your channel", "name-your-channel": "Name your channel",
@ -216,6 +217,15 @@
"community-image-pick": "Pick an image", "community-image-pick": "Pick an image",
"community-image-delete": "", "community-image-delete": "",
"community-image-remove": "Remove", "community-image-remove": "Remove",
"community-join-requirements-met": "Join requirements met",
"community-join-requirements-not-met": "Join requirements not met",
"community-join-requirements-changed": "Token requirements have changed",
"community-join-requirements-tokens-lost": "You no longer have the tokens required",
"community-channel-read-requirements-met": "View only requirements met",
"community-channel-read-requirements-not-met": "View only requirements not met",
"community-channel-write-requirements-met": "View and post requirements met",
"community-channel-write-requirements-not-met": "View and post requirements not met",
"community-enter-channel-info": "Entering channel will reveal your public addresses to the node owner",
"community-color": "Community colour", "community-color": "Community colour",
"community-link": "Community link", "community-link": "Community link",
"community-color-placeholder": "Pick a colour", "community-color-placeholder": "Pick a colour",
@ -434,6 +444,7 @@
"delete-profile": "Delete profile", "delete-profile": "Delete profile",
"delete-my-profile": "Delete my profile", "delete-my-profile": "Delete my profile",
"delete-profile-warning": "Warning: If you dont have your seed phrase written down, you will lose access to your funds after you delete your profile", "delete-profile-warning": "Warning: If you dont have your seed phrase written down, you will lose access to your funds after you delete your profile",
"enter-channel": "Enter channel",
"profile-deleted-title": "Profile deleted", "profile-deleted-title": "Profile deleted",
"profile-deleted-content": "Your profile was successfully deleted", "profile-deleted-content": "Your profile was successfully deleted",
"profile-deleted-keycard": "You can now restore another keypair on your Keycard", "profile-deleted-keycard": "You can now restore another keypair on your Keycard",
@ -1842,6 +1853,9 @@
"blank-messages-text": "Your messages will be here", "blank-messages-text": "Your messages will be here",
"blank-contacts-text": "Your contacts will be here", "blank-contacts-text": "Your contacts will be here",
"groups": "Groups", "groups": "Groups",
"you-must-hold": "You must hold:",
"you-must-now-hold": "You must now hold:",
"you-must-always-hold": "You must always hold:",
"shell-placeholder-title": "Your apps will run here", "shell-placeholder-title": "Your apps will run here",
"no-pinned-messages-desc": "This chat doesn't have any pinned messages.", "no-pinned-messages-desc": "This chat doesn't have any pinned messages.",
"no-pinned-messages-community-desc": "This channel doesn't have any pinned messages.", "no-pinned-messages-community-desc": "This channel doesn't have any pinned messages.",