mirror of
https://github.com/status-im/open-bounty.git
synced 2025-01-26 01:10:31 +00:00
Merge branch 'develop' into fix/metamask-address-not-updated-#187
This commit is contained in:
commit
186d989006
3
.dockerignore
Normal file
3
.dockerignore
Normal file
@ -0,0 +1,3 @@
|
||||
docker-compose.yml
|
||||
Dockerfile
|
||||
Jenkinsfile
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -23,3 +23,7 @@ profiles.clj
|
||||
.idea
|
||||
resources/contracts
|
||||
node_modules
|
||||
/config-prod.edn
|
||||
/config-dev.edn
|
||||
/config-test.edn
|
||||
/src/java
|
||||
|
30
Dockerfile
Normal file
30
Dockerfile
Normal file
@ -0,0 +1,30 @@
|
||||
FROM clojure as builder
|
||||
|
||||
WORKDIR /tmp
|
||||
|
||||
RUN wget -O /usr/local/bin/solc https://github.com/ethereum/solidity/releases/download/v0.4.15/solc-static-linux
|
||||
RUN chmod +x /usr/local/bin/solc
|
||||
|
||||
RUN wget https://github.com/web3j/web3j/releases/download/v2.3.0/web3j-2.3.0.tar
|
||||
RUN tar -xf web3j-2.3.0.tar
|
||||
RUN cp -r web3j-2.3.0/* /usr/local/
|
||||
|
||||
|
||||
COPY . /usr/src/app
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
ENV LEIN_SNAPSHOTS_IN_RELEASE=1
|
||||
|
||||
|
||||
RUN lein less once
|
||||
RUN lein uberjar
|
||||
|
||||
|
||||
FROM clojure
|
||||
WORKDIR /root/
|
||||
|
||||
COPY --from=builder /usr/src/app/target/uberjar/commiteth.jar .
|
||||
|
||||
CMD [""]
|
||||
ENTRYPOINT ["/usr/bin/java", "-Duser.timezone=UTC", "-Dconf=config-test.edn", "-jar", "/root/commiteth.jar"]
|
||||
|
35
Jenkinsfile
vendored
Normal file
35
Jenkinsfile
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env groovy
|
||||
|
||||
node('linux') {
|
||||
checkout scm
|
||||
|
||||
def dockerreponame = "statusim/openbounty-app"
|
||||
|
||||
try {
|
||||
stage('Build & push') {
|
||||
|
||||
GIT_COMMIT_HASH = sh (script: "git rev-parse --short HEAD | tr -d '\n'", returnStdout: true)
|
||||
|
||||
docker.withRegistry('https://index.docker.io/v1/', 'dockerhub-statusvan') {
|
||||
def openbountyApp = docker.build("${dockerreponame}:${env.BUILD_NUMBER}")
|
||||
openbountyApp.push("${env.BRANCH_NAME}")
|
||||
if (env.BRANCH_NAME == 'develop') {
|
||||
openbountyApp.push("develop")
|
||||
} else if (env.BRANCH_NAME == 'master') {
|
||||
openbountyApp.push("master")
|
||||
} else {
|
||||
println "Not named branch have no custom tag"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
stage('Deploy') {
|
||||
build job: 'status-openbounty/openbounty-cluster', parameters: [[$class: 'StringParameterValue', name: 'DEPLOY_ENVIRONMENT', value: "dev"], [$class: 'StringParameterValue', name: 'BRANCH', value: env.BRANCH_NAME]]
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
// slackSend color: 'bad', message: REPO + ":" + BRANCH_NAME + ' failed to build. ' + env.BUILD_URL
|
||||
throw e
|
||||
}
|
||||
}
|
44
README.md
44
README.md
@ -9,7 +9,6 @@ Live production version:
|
||||
https://openbounty.status.im
|
||||
The `master` branch is automatically deployed here.
|
||||
|
||||
|
||||
Live testnet (Ropsten) version:
|
||||
https://openbounty.status.im:444
|
||||
The `develop` branch is automatically deployed here.
|
||||
@ -17,10 +16,9 @@ The `develop` branch is automatically deployed here.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
You will need [Leiningen](https://github.com/technomancy/leiningen) 2.0 or above installed.
|
||||
You will need [Leiningen](https://github.com/technomancy/leiningen) 2.0 or above installed. Also, make sure that you have [wkhtmltoimage](https://wkhtmltopdf.org/downloads.html) available in your PATH. On macOS, it can be installed via `brew cask install wkhtmltopdf`.
|
||||
|
||||
### PostgreSQL
|
||||
<<<<<<< HEAD
|
||||
|
||||
Make sure you install [PostgreSQL](https://www.postgresql.org/) and properly set it up:
|
||||
|
||||
@ -39,7 +37,6 @@ lein figwheel
|
||||
lein less auto
|
||||
```
|
||||
|
||||
=======
|
||||
|
||||
Make sure you install [PostgreSQL](https://www.postgresql.org/) and properly set it up:
|
||||
|
||||
@ -56,9 +53,40 @@ Solidity compiler [0.4.15](https://github.com/ethereum/solidity/releases/tag/v0.
|
||||
|
||||
Web3j [2.3.0](https://github.com/web3j/web3j/releases/tag/v2.3.0) is required and the command line tools need to be in $PATH.
|
||||
|
||||
## Running
|
||||
## Application config
|
||||
|
||||
Make sure `env/dev/resources/config.edn` is correctly populated.
|
||||
Make sure to create `/config-dev.edn` and populate it correctly, which is based on `env/dev/resources/config.edn`. Description of config fields is given below:
|
||||
|
||||
Key | Description
|
||||
--- | ---
|
||||
dev | Currently specifies whether Swagger UI endpoints should be added to routes
|
||||
port | HTTP port for the Ring web app
|
||||
nrepl-port | nREPL port for development
|
||||
jdbc-database-url | PostgreSQL database URL. For instance, URL to local db would be `jdbc:postgresql://localhost/commiteth?user=commiteth&password=commiteth`
|
||||
server-address | URL and port of local server that can be resolved from public internet. It will be used as a redirect URI during GitHub OAuth authorization process
|
||||
eth-account | Ethereum account ID for the bot
|
||||
eth-password | Ethereum account password for the bot
|
||||
eth-rpc-url | RPC URL to Ethereum node, e.g. Geth. Either local or remote
|
||||
eth-wallet-file | Location of wallet file. If Geth is run with the parameters as given below, it will reside under `$HOME/.ropsten/keystore`
|
||||
tokenreg-base-format | Should be set to `:status`
|
||||
github-client-id | Related to OAuth. Copied from GitHub account Settings->Developer settings->OAuth Apps
|
||||
github-client-secret | Related to OAuth. Copied from GitHub account Settings->Developer settings->OAuth Apps
|
||||
github-user | GitHub username for bot account. It is used for posting bounty comments
|
||||
github-password | GitHub password for bot account
|
||||
webhook-secret | Secret string to be used when creating a GitHub App
|
||||
user-whitelist | Set of GitHub user/org IDs to be whitelisted. E.g. `#{"status-im" "your_org"}`
|
||||
testnet-token-data | Token data map, useful if there are Geth connectivity problems
|
||||
|
||||
## GitHub integration
|
||||
Open Bounty uses both OAuth App and GitHub App integration.
|
||||
|
||||
### OAuth App
|
||||
Follow the steps [here](https://developer.github.com/apps/building-oauth-apps/creating-an-oauth-app/). Specify the value of `:server-address` as "Homepage URL", and `:server-address` + `/callback` as "Authorization callback URL". Be sure to copy Client ID and Client Secret values in the config file.
|
||||
|
||||
### GitHub App
|
||||
Follow the steps [here](https://developer.github.com/apps/building-github-apps/creating-a-github-app/). Be sure to specify `:server-address` + `/webhook-app` as "Webhook URL", and `:webhook-secret` as "Webhook Secret".
|
||||
|
||||
## Running
|
||||
|
||||
Lauch a local geth node with the bot account unlocked:
|
||||
|
||||
@ -141,6 +169,10 @@ Landing page is static and different CSS and JS due to time constraints.
|
||||
|
||||
This copies over necessary artifacts to `resources` dir.
|
||||
|
||||
|
||||
### Troubleshooting
|
||||
See the [Cookbook](doc/cookbook.md).
|
||||
|
||||
## License
|
||||
|
||||
Licensed under the [Affero General Public License v3.0](https://github.com/status-im/commiteth/blob/master/LICENSE.md)
|
||||
|
18
doc/cookbook.md
Normal file
18
doc/cookbook.md
Normal file
@ -0,0 +1,18 @@
|
||||
# Cookbook
|
||||
|
||||
Here some common tasks/issues are listed along with their solutions.
|
||||
|
||||
## Change config and restart the service
|
||||
- ssh to the host running commiteth
|
||||
- go to `/opt/commiteth` for prod or `/opt/commiteth-test` for test environment.
|
||||
- there edit `config.edn`
|
||||
- restart the service with `sudo service commiteth-test restart`
|
||||
|
||||
## Manually add a GitHub repo to SOB app
|
||||
Sometimes SOB will not
|
||||
- connect to Postgres instance. Get DB name and username/password from respective `config.edn` (see previous answer). These are set in `:jdbc-database-url` field.
|
||||
- execute the query, e.g.
|
||||
```
|
||||
insert into repositories(repo_id,user_id,owner,repo,hook_id,state,hook_secret,owner_avatar_url) values(116971984,447328, 'aragon', 'aragon-monthly', 0, 2, '', 'https://avatars1.githubusercontent.com/u/24612534?v=4');
|
||||
```
|
||||
Note that the value for repo_id field is GitHub's internal repo ID. One way of obtaining it is navigating to `api.github.com/repos/:owner/:repo_name`, e.g. `https://api.github.com/repos/aragon/aragon-monthly`.
|
@ -29,13 +29,9 @@ You should now see `Bounties`, `Activity`, `Repositories` and `Manage Payouts` t
|
||||
|
||||
### Creating bounty issues
|
||||
|
||||
Before you can create bounties, you need to have administrative access to one or more GitHub repositories. These can be either in the scope of your personal user account or in the scope of a Github orgnazation.
|
||||
Before you can create bounties, you need to add Open Bounty GitHub App to your account or repos. Go to https://github.com/apps/status-open-bounty-app-test (or link to another GitHub App you've created for testing, as described in the [README](README.md) and click Install. Specify whether access to all org repos or specific repos is granted. This will install webhooks for SOB in your repos.
|
||||
|
||||
* click the `Repositories` tab
|
||||
* click on the button `Enable Github Account`
|
||||
* If you have 1 or more Organisation repositories then grant Organisation access to each of them by clicking on the button `Grant`
|
||||
* grant Status Open Bounty the needed addtional permissions for managing repository webhooks, adding and modifying comments by clicking on the button `Authorize status-open-bounty`
|
||||
* now you should see your repositories on the `Repositories` tab, click `Add` on one. If your account isn't whitelisted you will see instructions how to request an access. If your account is whitelisted then new `bounty` label will become available in the GitHub repository's labels and a new webhook should now exist for the repository.
|
||||
* Request for your account to be whitelisted. Contact [Riot](https://chat.status.im) for more information
|
||||
* now, add the `bounty` label to a new or an existing issue. This should cause Status Open Bounty to post a new comment for the issue containing an image with text `Deploying contract, please wait`
|
||||
* once the contract has been mined, the comment will be updated to contain the bounty contract's address and a QR code
|
||||
|
||||
@ -63,3 +59,11 @@ To remove issue from the Bounties list you can close it in GitHub.
|
||||
All bugs should be reported as issues in the [OpenBounty Github repository](https://github.com/status-im/open-bounty/issues).
|
||||
|
||||
Please first check that there is not already a duplicate issue. Issues should contain exact and minimal step-by-step instructions for reproducing the problem.
|
||||
|
||||
### Status Open Bounty end-to-end tests
|
||||
|
||||
Framework for testing located in: `open-bounty/test/end-to-end`
|
||||
|
||||
Full installation and configuring manual: [Status Open Bounty end-to-end tests](https://wiki.status.im/Status_Open_Bounty_end-to-end_tests)
|
||||
|
||||
Currently supports local and Jenkins environment running (you can find example of JenkinsFile in `open-bounty/test` )
|
||||
|
12
env/dev/resources/config.edn
vendored
12
env/dev/resources/config.edn
vendored
@ -11,16 +11,18 @@
|
||||
:eth-password "XXX"
|
||||
|
||||
;; RPC URL to ethereum node to be used
|
||||
:eth-rpc-url "http://localhost:8547"
|
||||
:eth-rpc-url "http://localhost:8545"
|
||||
:eth-wallet-file "/some/location"
|
||||
|
||||
;; address of token registry to be used
|
||||
:tokenreg-addr "0x..."
|
||||
;; this is the default value for ropsten
|
||||
:tokenreg-addr "0x7d127a3e3b5e72cd8f15e7dee650abe4fcced2b9"
|
||||
;; format of tokenreg records' base field, possible values :status, :parity
|
||||
:tokenreg-base-format :parity
|
||||
:tokenreg-base-format :status
|
||||
|
||||
;; address of factory contract used for deploying bounty contracts
|
||||
:contract-factory-addr "0x..."
|
||||
;; this is the default value for ropsten
|
||||
:contract-factory-addr "0x3B9A3c062Bdb640b5039C0cCda4157737d732F95"
|
||||
|
||||
;; commiteth-test-tpatja
|
||||
:github-client-id "CLIENT ID"
|
||||
@ -43,5 +45,7 @@
|
||||
;; needeed when :hubspot-contact-create-enabled
|
||||
:hubspot-api-key "xxxxxxx-xxxx-x-xxxx-xxxx"
|
||||
|
||||
:user-whitelist #{}
|
||||
|
||||
;; used for blacklisting tokens from token registry data
|
||||
:token-blacklist #{}}
|
||||
|
@ -94,7 +94,8 @@
|
||||
|
||||
|
||||
:profiles
|
||||
{:uberjar {:omit-source true
|
||||
{:uberjar {:jvm-opts ["-server" "-Dconf=config-prod.edn"]
|
||||
:omit-source true
|
||||
:prep-tasks ["build-contracts" "javac" "compile" ["cljsbuild" "once" "min"] ["less" "once"]]
|
||||
:cljsbuild
|
||||
{:builds
|
||||
@ -116,7 +117,8 @@
|
||||
:uberjar-name "commiteth.jar"
|
||||
:source-paths ["env/prod/clj"]
|
||||
:resource-paths ["env/prod/resources"]}
|
||||
:dev {:dependencies [[prone "1.1.4"]
|
||||
:dev {:jvm-opts ["-server" "-Dconf=config-dev.edn"]
|
||||
:dependencies [[prone "1.1.4"]
|
||||
[ring/ring-mock "0.3.1"]
|
||||
[ring/ring-devel "1.6.2"]
|
||||
[pjstadig/humane-test-output "0.8.3"]
|
||||
@ -150,7 +152,8 @@
|
||||
:nrepl-middleware [cemerick.piggieback/wrap-cljs-repl]}
|
||||
:injections [(require 'pjstadig.humane-test-output)
|
||||
(pjstadig.humane-test-output/activate!)]}
|
||||
:test {:resource-paths ["env/dev/resources" "env/test/resources"]
|
||||
:test {:jvm-opts ["-server" "-Dconf=config-test.edn"]
|
||||
:resource-paths ["env/dev/resources" "env/test/resources"]
|
||||
:dependencies [[devcards "0.2.4"]]
|
||||
:cljsbuild
|
||||
{:builds
|
||||
|
6
resources/public/bounty-filter-remove.svg
Normal file
6
resources/public/bounty-filter-remove.svg
Normal file
@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<rect width="20" height="20" fill="#000" opacity=".5" rx="6"/>
|
||||
<path fill="#57A7ED" d="M10 8.812L6.436 5.247a.839.839 0 0 0-1.19 0 .839.839 0 0 0 .001 1.189L8.812 10l-3.565 3.564a.839.839 0 0 0 0 1.19c.33.33.86.327 1.189-.001L10 11.188l3.564 3.565c.33.33.861.328 1.19 0a.839.839 0 0 0-.001-1.189L11.188 10l3.565-3.564a.839.839 0 0 0 0-1.19.839.839 0 0 0-1.189.001L10 8.812z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 533 B |
3
resources/public/icon-forward-white.svg
Normal file
3
resources/public/icon-forward-white.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="7" viewBox="0 0 12 7">
|
||||
<path fill="#8D99A4" fill-rule="evenodd" d="M6 3.828L2.462.291A1.003 1.003 0 0 0 1.05.293a.996.996 0 0 0-.002 1.412l4.247 4.247c.192.192.447.289.702.29a.981.981 0 0 0 .708-.29l4.247-4.247A1.003 1.003 0 0 0 10.95.293.996.996 0 0 0 9.538.29L6 3.828z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 344 B |
@ -29,7 +29,7 @@
|
||||
:confirmation (eth/event-sig->topic-id "Confirmation(address,uint256)")})
|
||||
|
||||
(defn factory-contract-addr []
|
||||
(env :contract-factory-addr "0x47F56FD26EEeCda4FdF5DB5843De1fe75D2A64A6"))
|
||||
(env :contract-factory-addr))
|
||||
|
||||
(defn tokenreg-base-format
|
||||
;; status tokenreg uses eg :base 18, while parity uses :base 1000000000000
|
||||
|
@ -1,26 +1,30 @@
|
||||
(ns commiteth.bounties
|
||||
(:require [re-frame.core :as rf]
|
||||
[reagent.core :as r]
|
||||
(:require [reagent.core :as r]
|
||||
[re-frame.core :as rf]
|
||||
[commiteth.common :refer [moment-timestamp
|
||||
display-data-page
|
||||
items-per-page
|
||||
issue-url]]))
|
||||
issue-url]]
|
||||
[commiteth.handlers :as handlers]
|
||||
[commiteth.db :as db]
|
||||
[commiteth.ui-model :as ui-model]
|
||||
[commiteth.subscriptions :as subs]))
|
||||
|
||||
|
||||
(defn bounty-item [bounty]
|
||||
(let [{avatar-url :repo_owner_avatar_url
|
||||
owner :repo-owner
|
||||
repo-name :repo-name
|
||||
issue-title :issue-title
|
||||
(let [{avatar-url :repo_owner_avatar_url
|
||||
owner :repo-owner
|
||||
repo-name :repo-name
|
||||
issue-title :issue-title
|
||||
issue-number :issue-number
|
||||
updated :updated
|
||||
tokens :tokens
|
||||
balance-eth :balance-eth
|
||||
value-usd :value-usd
|
||||
claim-count :claim-count} bounty
|
||||
full-repo (str owner "/" repo-name)
|
||||
repo-url (str "https://github.com/" full-repo)
|
||||
repo-link [:a {:href repo-url} full-repo]
|
||||
updated :updated
|
||||
tokens :tokens
|
||||
balance-eth :balance-eth
|
||||
value-usd :value-usd
|
||||
claim-count :claim-count} bounty
|
||||
full-repo (str owner "/" repo-name)
|
||||
repo-url (str "https://github.com/" full-repo)
|
||||
repo-link [:a {:href repo-url} full-repo]
|
||||
issue-link [:a
|
||||
{:href (issue-url owner repo-name issue-number)}
|
||||
issue-title]]
|
||||
@ -40,35 +44,187 @@
|
||||
[:span.usd-value-label "Value "] [:span.usd-balance-label (str "$" value-usd)]
|
||||
(when (> claim-count 0)
|
||||
[:span.open-claims-label (str claim-count " open claim"
|
||||
(when (> claim-count 1) "s"))]) ]]
|
||||
(when (> claim-count 1) "s"))])]]
|
||||
[:div.open-bounty-item-icon
|
||||
[:div.ui.tiny.circular.image
|
||||
[:img {:src avatar-url}]]]]))
|
||||
|
||||
(defn bounties-list [{:keys [items item-count page-number total-count]
|
||||
:as bounty-page-data}
|
||||
(defn bounties-filter-tooltip-value-input-view [label tooltip-open? opts]
|
||||
[:div.open-bounties-filter-element-tooltip-value-input-container
|
||||
[:div.:input.open-bounties-filter-element-tooltip-value-input-label
|
||||
label]
|
||||
[:input.open-bounties-filter-element-tooltip-value-input
|
||||
{:type "range"
|
||||
:min (:min opts)
|
||||
:max (:max opts)
|
||||
:step (:step opts)
|
||||
:value (:current-val opts)
|
||||
:on-change (when-let [f (:on-change-val opts)]
|
||||
#(-> % .-target .-value int f))
|
||||
:on-focus #(reset! tooltip-open? true)}]])
|
||||
|
||||
(defmulti bounties-filter-tooltip-view #(-> %2 ::ui-model/bounty-filter-type.category))
|
||||
|
||||
(defmethod bounties-filter-tooltip-view ::ui-model/bounty-filter-type-category|range
|
||||
[filter-type filter-type-def current-filter-value tooltip-open?]
|
||||
(let [default-min (::ui-model/bounty-filter-type.min-val filter-type-def)
|
||||
default-max (::ui-model/bounty-filter-type.max-val filter-type-def)
|
||||
common-range-opts {:min default-min :max default-max}
|
||||
current-min (or (first current-filter-value) default-min)
|
||||
current-max (or (second current-filter-value) default-max)
|
||||
on-change-fn (fn [min-val max-val]
|
||||
(rf/dispatch [::handlers/set-open-bounty-filter-type
|
||||
filter-type
|
||||
[(min min-val default-max)
|
||||
(max max-val default-min)]]))
|
||||
on-min-change-fn (fn [new-min]
|
||||
(let [new-max (max current-max (min default-max new-min))]
|
||||
(on-change-fn new-min new-max)))
|
||||
on-max-change-fn (fn [new-max]
|
||||
(let [new-min (min current-min (max default-min new-max))]
|
||||
(on-change-fn new-min new-max)))]
|
||||
[:div.open-bounties-filter-list
|
||||
(::ui-model/bounty-filter.type.header filter-type-def)
|
||||
[bounties-filter-tooltip-value-input-view "Min" tooltip-open? (merge common-range-opts
|
||||
{:current-val current-min
|
||||
:on-change-val on-min-change-fn})]
|
||||
[bounties-filter-tooltip-value-input-view "Max" tooltip-open? (merge common-range-opts
|
||||
{:current-val current-max
|
||||
:on-change-val on-max-change-fn})]]))
|
||||
|
||||
(defmethod bounties-filter-tooltip-view ::ui-model/bounty-filter-type-category|single-static-option
|
||||
[filter-type filter-type-def current-filter-value tooltip-open?]
|
||||
[:div.open-bounties-filter-list
|
||||
(for [[option-type option-text] (::ui-model/bounty-filter-type.options filter-type-def)]
|
||||
^{:key (str option-type)}
|
||||
[:div.open-bounties-filter-list-option
|
||||
(merge {:on-click #(do (rf/dispatch [::handlers/set-open-bounty-filter-type
|
||||
filter-type
|
||||
option-type])
|
||||
(reset! tooltip-open? false))
|
||||
:tab-index 0
|
||||
:on-focus #(reset! tooltip-open? true)}
|
||||
(when (= option-type current-filter-value)
|
||||
{:class "active"}))
|
||||
option-text])])
|
||||
|
||||
(defmethod bounties-filter-tooltip-view ::ui-model/bounty-filter-type-category|multiple-dynamic-options
|
||||
[filter-type filter-type-def current-filter-value tooltip-open?]
|
||||
(let [options (rf/subscribe [(::ui-model/bounty-filter-type.re-frame-subs-key-for-options filter-type-def)])]
|
||||
[:div.open-bounties-filter-list
|
||||
(for [option @options]
|
||||
(let [active? (boolean (and current-filter-value (current-filter-value option)))]
|
||||
^{:key (str option)}
|
||||
[:div.open-bounties-filter-list-option-checkbox
|
||||
[:label
|
||||
{:on-click #(rf/dispatch [::handlers/set-open-bounty-filter-type
|
||||
filter-type
|
||||
(cond
|
||||
(and active? (= #{option} current-filter-value)) nil
|
||||
active? (disj current-filter-value option)
|
||||
:else (into #{option} current-filter-value))])
|
||||
:tab-index 0
|
||||
:on-focus #(do (.stopPropagation %) (reset! tooltip-open? true))}
|
||||
[:input
|
||||
{:type "checkbox"
|
||||
:checked active?
|
||||
:on-focus #(reset! tooltip-open? true)}]
|
||||
[:div.text option]]]))]))
|
||||
|
||||
(defn bounty-filter-view [filter-type current-filter-value]
|
||||
(let [tooltip-open? (r/atom false)]
|
||||
(fn [filter-type current-filter-value]
|
||||
[:div.open-bounties-filter-element-container
|
||||
{:tab-index 0
|
||||
:on-focus #(reset! tooltip-open? true)
|
||||
:on-blur #(reset! tooltip-open? false)}
|
||||
[:div.open-bounties-filter-element
|
||||
{:on-mouse-down #(swap! tooltip-open? not)
|
||||
:class (when (or current-filter-value @tooltip-open?)
|
||||
"open-bounties-filter-element-active")}
|
||||
[:div.text
|
||||
(if current-filter-value
|
||||
(ui-model/bounty-filter-value->short-text filter-type current-filter-value)
|
||||
(ui-model/bounty-filter-type->name filter-type))]
|
||||
(when current-filter-value
|
||||
[:div.remove-container
|
||||
{:tab-index 0
|
||||
:on-focus #(.stopPropagation %)}
|
||||
[:img.remove
|
||||
{:src "bounty-filter-remove.svg"
|
||||
:on-mouse-down (fn [e]
|
||||
(.stopPropagation e)
|
||||
(rf/dispatch [::handlers/set-open-bounty-filter-type filter-type nil])
|
||||
(reset! tooltip-open? false))}]])]
|
||||
(when @tooltip-open?
|
||||
[:div.open-bounties-filter-element-tooltip
|
||||
[bounties-filter-tooltip-view
|
||||
filter-type
|
||||
(ui-model/bounty-filter-types-def filter-type)
|
||||
current-filter-value
|
||||
tooltip-open?]])])))
|
||||
|
||||
(defn bounty-filters-view []
|
||||
(let [current-filters (rf/subscribe [::subs/open-bounties-filters])]
|
||||
[:div.open-bounties-filter
|
||||
; doall because derefs are not supported in lazy seqs: https://github.com/reagent-project/reagent/issues/18
|
||||
(doall
|
||||
(for [filter-type ui-model/bounty-filter-types]
|
||||
^{:key (str filter-type)}
|
||||
[bounty-filter-view
|
||||
filter-type
|
||||
(get @current-filters filter-type)]))]))
|
||||
|
||||
(defn bounties-sort-view []
|
||||
(let [open? (r/atom false)]
|
||||
(fn []
|
||||
(let [current-sorting (rf/subscribe [::subs/open-bounties-sorting-type])]
|
||||
[:div.open-bounties-sort
|
||||
{:tab-index 0
|
||||
:on-blur #(reset! open? false)}
|
||||
[:div.open-bounties-sort-element
|
||||
{:on-click #(swap! open? not)}
|
||||
(ui-model/bounty-sorting-type->name @current-sorting)
|
||||
[:div.icon-forward-white-box
|
||||
[:img
|
||||
{:src "icon-forward-white.svg"}]]]
|
||||
(when @open?
|
||||
[:div.open-bounties-sort-element-tooltip
|
||||
(for [sorting-type (keys ui-model/bounty-sorting-types-def)]
|
||||
^{:key (str sorting-type)}
|
||||
[:div.open-bounties-sort-type
|
||||
{:on-click #(do
|
||||
(reset! open? false)
|
||||
(rf/dispatch [::handlers/set-open-bounties-sorting-type sorting-type]))}
|
||||
(ui-model/bounty-sorting-type->name sorting-type)])])]))))
|
||||
|
||||
(defn bounties-list [{:keys [items item-count page-number total-count]
|
||||
:as bounty-page-data}
|
||||
container-element]
|
||||
(if (empty? items)
|
||||
[:div.view-no-data-container
|
||||
[:p "No recent activity yet"]]
|
||||
[:p "No matching bounties found."]]
|
||||
[:div
|
||||
(let [left (inc (* (dec page-number) items-per-page))
|
||||
(let [left (inc (* (dec page-number) items-per-page))
|
||||
right (dec (+ left item-count))]
|
||||
[:div.item-counts-label
|
||||
[:span (str "Showing " left "-" right " of " total-count)]])
|
||||
(display-data-page bounty-page-data bounty-item container-element)]))
|
||||
|
||||
(defn bounties-page []
|
||||
(let [bounty-page-data (rf/subscribe [:open-bounties-page])
|
||||
(let [bounty-page-data (rf/subscribe [:open-bounties-page])
|
||||
open-bounties-loading? (rf/subscribe [:get-in [:open-bounties-loading?]])
|
||||
container-element (atom nil)]
|
||||
container-element (atom nil)]
|
||||
(fn []
|
||||
(if @open-bounties-loading?
|
||||
[:div.view-loading-container
|
||||
[:div.ui.active.inverted.dimmer
|
||||
[:div.ui.text.loader.view-loading-label "Loading"]]]
|
||||
[:div.ui.container.open-bounties-container
|
||||
{:ref #(reset! container-element %1)}
|
||||
{:ref #(reset! container-element %1)}
|
||||
[:div.open-bounties-header "Bounties"]
|
||||
[:div.open-bounties-filter-and-sort
|
||||
[bounty-filters-view]
|
||||
[bounties-sort-view]]
|
||||
[bounties-list @bounty-page-data container-element]]))
|
||||
))
|
||||
|
@ -12,17 +12,16 @@
|
||||
:on-change #(reset! val-ratom (-> % .-target .-value))})]))
|
||||
|
||||
(defn dropdown [props title val-ratom items]
|
||||
"If val-ratom is set, preselect it in the dropdown.
|
||||
Otherwise, prepend title as a disabled option."
|
||||
(fn []
|
||||
(if (= 1 (count items))
|
||||
(reset! val-ratom (first items)))
|
||||
[:select.ui.basic.selection.dropdown
|
||||
(merge props {:on-change
|
||||
#(reset! val-ratom (-> % .-target .-value))
|
||||
:default-value (or @val-ratom title)})
|
||||
(for [item items]
|
||||
^{:key item} [:option {:value item
|
||||
:disabled (= item title)}
|
||||
item])]))
|
||||
#(reset! val-ratom (-> % .-target .-value))})
|
||||
(doall (for [item items]
|
||||
^{:key item} [:option
|
||||
{:value item}
|
||||
item]))]))
|
||||
|
||||
(defn moment-timestamp [time]
|
||||
(let [now (.now js/Date.)
|
||||
|
@ -1,15 +1,24 @@
|
||||
(ns commiteth.db)
|
||||
(ns commiteth.db
|
||||
(:require [commiteth.ui-model :as ui-model]))
|
||||
|
||||
(def default-db
|
||||
{:page :bounties
|
||||
:user nil
|
||||
{:page :bounties
|
||||
:user nil
|
||||
:user-profile-loaded? false
|
||||
:repos-loading? false
|
||||
:repos {}
|
||||
:activity-feed-loading? false
|
||||
:open-bounties-loading? false
|
||||
:open-bounties []
|
||||
:page-number 1
|
||||
:owner-bounties {}
|
||||
:top-hunters []
|
||||
:activity-feed []})
|
||||
:repos-loading? false
|
||||
:repos {}
|
||||
:activity-feed-loading? false
|
||||
:open-bounties-loading? false
|
||||
:open-bounties []
|
||||
:page-number 1
|
||||
:bounty-page-number 1
|
||||
:activity-page-number 1
|
||||
::open-bounties-sorting-type ::ui-model/bounty-sorting-type|most-recent
|
||||
::open-bounties-filters {::ui-model/bounty-filter-type|value nil
|
||||
::ui-model/bounty-filter-type|currency nil
|
||||
::ui-model/bounty-filter-type|date nil
|
||||
::ui-model/bounty-filter-type|owner nil}
|
||||
:owner-bounties {}
|
||||
:top-hunters []
|
||||
:activity-feed []})
|
||||
>>>>>>> develop
|
||||
|
@ -11,7 +11,8 @@
|
||||
[cljs-web3.eth :as web3-eth]
|
||||
[akiroz.re-frame.storage
|
||||
:as rf-storage
|
||||
:refer [reg-co-fx!]]))
|
||||
:refer [reg-co-fx!]]
|
||||
[commiteth.ui-model :as ui-model]))
|
||||
|
||||
|
||||
(rf-storage/reg-co-fx! :commiteth-sob {:fx :store
|
||||
@ -63,10 +64,12 @@
|
||||
|
||||
|
||||
(reg-event-db
|
||||
:set-active-page
|
||||
(fn [db [_ page]]
|
||||
(assoc db :page page
|
||||
:page-number 1)))
|
||||
:set-active-page
|
||||
(fn [db [_ page]]
|
||||
(assoc db :page page
|
||||
:page-number 1
|
||||
::db/open-bounties-filters {}
|
||||
::db/open-bounties-sorting-type ::ui-model/bounty-sorting-type|most-recent)))
|
||||
|
||||
(reg-event-db
|
||||
:set-page-number
|
||||
@ -325,7 +328,7 @@
|
||||
:http {:method POST
|
||||
:url "/api/user/address"
|
||||
:on-success #(do
|
||||
(dispatch [:assoc-in [:user :address] address])
|
||||
(dispatch [:assoc-in [:user [:address] address]])
|
||||
(dispatch [:set-flash-message
|
||||
:success
|
||||
"Address saved"]))
|
||||
@ -458,3 +461,16 @@
|
||||
(fn [db [_]]
|
||||
(.removeEventListener js/window "click" close-dropdown)
|
||||
(assoc db :user-dropdown-open? false)))
|
||||
|
||||
(reg-event-db
|
||||
::set-open-bounties-sorting-type
|
||||
(fn [db [_ sorting-type]]
|
||||
(merge db {::db/open-bounties-sorting-type sorting-type
|
||||
:page-number 1})))
|
||||
|
||||
(reg-event-db
|
||||
::set-open-bounty-filter-type
|
||||
(fn [db [_ filter-type filter-value]]
|
||||
(-> db
|
||||
(assoc-in [::db/open-bounties-filters filter-type] filter-value)
|
||||
(assoc :page-number 1))))
|
||||
|
@ -1,10 +1,13 @@
|
||||
(ns commiteth.subscriptions
|
||||
(:require [re-frame.core :refer [reg-sub]]
|
||||
[commiteth.common :refer [items-per-page]]))
|
||||
[commiteth.db :as db]
|
||||
[commiteth.ui-model :as ui-model]
|
||||
[commiteth.common :refer [items-per-page]]
|
||||
[clojure.string :as string]))
|
||||
|
||||
(reg-sub
|
||||
:db
|
||||
(fn [db _] db))
|
||||
:db
|
||||
(fn [db _] db))
|
||||
|
||||
(reg-sub
|
||||
:page
|
||||
@ -48,7 +51,7 @@
|
||||
|
||||
(reg-sub
|
||||
:open-bounties-page
|
||||
:<- [:open-bounties]
|
||||
:<- [::filtered-and-sorted-open-bounties]
|
||||
:<- [:page-number]
|
||||
(fn [[open-bounties page-number] _]
|
||||
(let [total-count (count open-bounties)
|
||||
@ -110,16 +113,62 @@
|
||||
(get-in db path)))
|
||||
|
||||
(reg-sub
|
||||
:usage-metrics
|
||||
(fn [db _]
|
||||
(:usage-metrics db)))
|
||||
:usage-metrics
|
||||
(fn [db _]
|
||||
(:usage-metrics db)))
|
||||
|
||||
(reg-sub
|
||||
:metrics-loading?
|
||||
(fn [db _]
|
||||
(:metrics-loading? db)))
|
||||
:metrics-loading?
|
||||
(fn [db _]
|
||||
(:metrics-loading? db)))
|
||||
|
||||
(reg-sub
|
||||
:user-dropdown-open?
|
||||
:user-dropdown-open?
|
||||
(fn [db _]
|
||||
(:user-dropdown-open? db)))
|
||||
|
||||
(reg-sub
|
||||
::open-bounties-sorting-type
|
||||
(fn [db _]
|
||||
(::db/open-bounties-sorting-type db)))
|
||||
|
||||
(reg-sub
|
||||
::open-bounties-filters
|
||||
(fn [db _]
|
||||
(::db/open-bounties-filters db)))
|
||||
|
||||
(reg-sub
|
||||
::open-bounties-owners
|
||||
:<- [:open-bounties]
|
||||
(fn [open-bounties _]
|
||||
(->> open-bounties
|
||||
(map :repo-owner)
|
||||
set)))
|
||||
|
||||
(reg-sub
|
||||
::open-bounties-owners-sorted
|
||||
:<- [::open-bounties-owners]
|
||||
(fn [owners _]
|
||||
(sort-by string/lower-case owners)))
|
||||
|
||||
(reg-sub
|
||||
::open-bounties-currencies
|
||||
:<- [:open-bounties]
|
||||
(fn [open-bounties _]
|
||||
(let [token-ids (->> open-bounties
|
||||
(map :tokens)
|
||||
(mapcat keys)
|
||||
(filter identity)
|
||||
set)]
|
||||
(into #{"ETH"} token-ids))))
|
||||
|
||||
(reg-sub
|
||||
::filtered-and-sorted-open-bounties
|
||||
:<- [:open-bounties]
|
||||
:<- [::open-bounties-filters]
|
||||
:<- [::open-bounties-sorting-type]
|
||||
(fn [[open-bounties filters sorting-type] _]
|
||||
(cond->> open-bounties
|
||||
true (ui-model/filter-bounties filters)
|
||||
sorting-type (ui-model/sort-bounties-by-sorting-type sorting-type)
|
||||
true vec)))
|
||||
|
165
src/cljs/commiteth/ui_model.cljs
Normal file
165
src/cljs/commiteth/ui_model.cljs
Normal file
@ -0,0 +1,165 @@
|
||||
(ns commiteth.ui-model
|
||||
(:require [clojure.set :as set]
|
||||
[cljs-time.core :as t]
|
||||
[cljs-time.coerce :as t-coerce]
|
||||
[cljs-time.format :as t-format]))
|
||||
|
||||
;;;; bounty sorting types
|
||||
|
||||
(def bounty-sorting-types-def
|
||||
{::bounty-sorting-type|most-recent {::bounty-sorting-type.name "Most recent"
|
||||
::bounty-sorting-type.sort-key-fn (fn [bounty]
|
||||
(:updated bounty))
|
||||
::bounty-sorting-type.sort-comparator-fn (comp - compare)}
|
||||
::bounty-sorting-type|lowest-value {::bounty-sorting-type.name "Lowest value"
|
||||
::bounty-sorting-type.sort-key-fn (fn [bounty]
|
||||
(js/parseFloat (:value-usd bounty)))
|
||||
::bounty-sorting-type.sort-comparator-fn compare}
|
||||
::bounty-sorting-type|highest-value {::bounty-sorting-type.name "Highest value"
|
||||
::bounty-sorting-type.sort-key-fn (fn [bounty]
|
||||
(js/parseFloat (:value-usd bounty)))
|
||||
::bounty-sorting-type.sort-comparator-fn (comp - compare)}
|
||||
::bounty-sorting-type|owner {::bounty-sorting-type.name "Owner"
|
||||
::bounty-sorting-type.sort-key-fn (fn [bounty]
|
||||
(:repo-owner bounty))
|
||||
::bounty-sorting-type.sort-comparator-fn compare}})
|
||||
|
||||
(defn bounty-sorting-type->name [sorting-type]
|
||||
(-> bounty-sorting-types-def (get sorting-type) ::bounty-sorting-type.name))
|
||||
|
||||
(defn sort-bounties-by-sorting-type [sorting-type bounties]
|
||||
(let [keyfn (-> bounty-sorting-types-def
|
||||
sorting-type
|
||||
::bounty-sorting-type.sort-key-fn)
|
||||
comparator (-> bounty-sorting-types-def
|
||||
sorting-type
|
||||
::bounty-sorting-type.sort-comparator-fn)]
|
||||
(sort-by keyfn comparator bounties)))
|
||||
|
||||
;;;; bounty filter types
|
||||
|
||||
(def bounty-filter-type-date-options-def {::bounty-filter-type-date-option|last-week "Last week"
|
||||
::bounty-filter-type-date-option|last-month "Last month"
|
||||
::bounty-filter-type-date-option|last-3-months "Last 3 months"})
|
||||
|
||||
(def bounty-filter-type-date-options (keys bounty-filter-type-date-options-def))
|
||||
|
||||
(defn bounty-filter-type-date-option->name [option]
|
||||
(bounty-filter-type-date-options-def option))
|
||||
|
||||
(def bounty-filter-type-date-pre-predicate-value-processor
|
||||
"It converts an option of the filter type date to a cljs-time interval in which
|
||||
that option is valid, so that you can check `cljs-time.core.within?` against that
|
||||
interval and know if a `cljs-time` date is valid for that filter type date option."
|
||||
(fn [filter-value]
|
||||
(let [filter-from (condp = filter-value
|
||||
::bounty-filter-type-date-option|last-week (t/minus (t/now) (t/weeks 1))
|
||||
::bounty-filter-type-date-option|last-month (t/minus (t/now) (t/months 1))
|
||||
::bounty-filter-type-date-option|last-3-months (t/minus (t/now) (t/months 3)))]
|
||||
(t/interval filter-from (t/now)))))
|
||||
(def bounty-filter-type-date-predicate
|
||||
(fn [filter-value-interval bounty]
|
||||
(when-let [date-inst (:updated bounty)]
|
||||
(let [date (-> date-inst inst-ms t-coerce/from-long)]
|
||||
(t/within? filter-value-interval date)))))
|
||||
|
||||
(def bounty-filter-type-claims-options-def {::bounty-filter-type-claims-option|no-claims "Not claimed yet"})
|
||||
|
||||
(def bounty-filter-type-claims-options (keys bounty-filter-type-claims-options-def))
|
||||
|
||||
(defn bounty-filter-type-claims-option->name [option]
|
||||
(bounty-filter-type-claims-options-def option))
|
||||
|
||||
(def bounty-filter-types-def
|
||||
{::bounty-filter-type|value
|
||||
{::bounty-filter-type.name "Value"
|
||||
::bounty-filter-type.category ::bounty-filter-type-category|range
|
||||
::bounty-filter-type.min-val 0
|
||||
::bounty-filter-type.max-val 10000
|
||||
::bounty-filter.type.header "$0 - $10000+"
|
||||
::bounty-filter-type.predicate (fn [filter-value bounty]
|
||||
(let [min-val (first filter-value)
|
||||
max-val (second filter-value)]
|
||||
(<= min-val (:value-usd bounty) max-val)))}
|
||||
|
||||
::bounty-filter-type|currency
|
||||
{::bounty-filter-type.name "Currency"
|
||||
::bounty-filter-type.category ::bounty-filter-type-category|multiple-dynamic-options
|
||||
::bounty-filter-type.re-frame-subs-key-for-options :commiteth.subscriptions/open-bounties-currencies
|
||||
::bounty-filter-type.predicate (fn [filter-value bounty]
|
||||
(and (or (not-any? #{"ETH"} filter-value)
|
||||
(< 0 (:balance-eth bounty)))
|
||||
(set/subset? (->> filter-value (remove #{"ETH"}) set)
|
||||
(-> bounty :tokens keys set))))}
|
||||
|
||||
::bounty-filter-type|date
|
||||
{::bounty-filter-type.name "Date"
|
||||
::bounty-filter-type.category ::bounty-filter-type-category|single-static-option
|
||||
::bounty-filter-type.options bounty-filter-type-date-options-def
|
||||
::bounty-filter-type.pre-predicate-value-processor bounty-filter-type-date-pre-predicate-value-processor
|
||||
::bounty-filter-type.predicate bounty-filter-type-date-predicate}
|
||||
|
||||
::bounty-filter-type|owner
|
||||
{::bounty-filter-type.name "Owner"
|
||||
::bounty-filter-type.category ::bounty-filter-type-category|multiple-dynamic-options
|
||||
::bounty-filter-type.re-frame-subs-key-for-options :commiteth.subscriptions/open-bounties-owners-sorted
|
||||
::bounty-filter-type.predicate (fn [filter-value bounty]
|
||||
(->> filter-value
|
||||
(some #{(:repo-owner bounty)})
|
||||
boolean))}
|
||||
|
||||
::bounty-filter-type|claims
|
||||
{::bounty-filter-type.name "Claims"
|
||||
::bounty-filter-type.category ::bounty-filter-type-category|single-static-option
|
||||
::bounty-filter-type.options bounty-filter-type-claims-options-def
|
||||
::bounty-filter-type.predicate (fn [filter-value bounty]
|
||||
(condp = filter-value
|
||||
::bounty-filter-type-claims-option|no-claims
|
||||
(= 0 (:claim-count bounty))))}})
|
||||
|
||||
(def bounty-filter-types (keys bounty-filter-types-def))
|
||||
|
||||
(defn bounty-filter-type->name [filter-type]
|
||||
(-> bounty-filter-types-def (get filter-type) ::bounty-filter-type.name))
|
||||
|
||||
(defn bounty-filter-value->short-text [filter-type filter-value]
|
||||
(cond
|
||||
(= filter-type ::bounty-filter-type|date)
|
||||
(bounty-filter-type-date-option->name filter-value)
|
||||
|
||||
(#{::bounty-filter-type|owner
|
||||
::bounty-filter-type|currency} filter-type)
|
||||
(str (bounty-filter-type->name filter-type) " (" (count filter-value) ")")
|
||||
|
||||
(= filter-type ::bounty-filter-type|value)
|
||||
(str "$" (first filter-value) "-$" (second filter-value))
|
||||
|
||||
(= filter-type ::bounty-filter-type|claims)
|
||||
(bounty-filter-type-claims-option->name filter-value)
|
||||
|
||||
:else
|
||||
(str filter-type " with val " filter-value)))
|
||||
|
||||
(defn- bounty-filter-values-by-type->predicates [filters-by-type]
|
||||
"It receives a map with filter types as keys and filter values as values and
|
||||
returns a lazy seq of predicates, one for each pair of filter type and value.
|
||||
Those predicate can receive a bounty and tell whether that bounty passes
|
||||
the filter type with that filter value. It removes filter types with a `nil`
|
||||
filter value."
|
||||
(->> filters-by-type
|
||||
; used `nil?` because a valid filter value can be `false`
|
||||
(remove #(nil? (val %)))
|
||||
(map (fn [[filter-type filter-value]]
|
||||
(let [filter-type-def (bounty-filter-types-def filter-type)
|
||||
pred (::bounty-filter-type.predicate filter-type-def)
|
||||
pre-pred-processor (::bounty-filter-type.pre-predicate-value-processor filter-type-def)
|
||||
filter-value (cond-> filter-value
|
||||
pre-pred-processor pre-pred-processor)]
|
||||
(partial pred filter-value))))))
|
||||
|
||||
(defn filter-bounties [filters-by-type bounties]
|
||||
(let [filter-preds (bounty-filter-values-by-type->predicates filters-by-type)
|
||||
filters-pred (fn [bounty]
|
||||
(every? #(% bounty) filter-preds))]
|
||||
(cond->> bounties
|
||||
(not-empty filter-preds) (filter filters-pred))))
|
@ -3,7 +3,6 @@
|
||||
[commiteth.common :refer [input dropdown]]
|
||||
[reagent.core :as r]
|
||||
[reagent.crypt :as crypt]
|
||||
[clojure.string :as str]
|
||||
[cljs-web3.eth :as web3-eth]))
|
||||
|
||||
(defn update-address-page-contents []
|
||||
@ -22,25 +21,13 @@
|
||||
[:p "Insert your Ethereum address in hex format."]
|
||||
[:div.field
|
||||
(if-not (empty? web3-accounts)
|
||||
; Add value of address if it's missing from items list.
|
||||
; If address is empty, add title
|
||||
(let [accounts (map str/lower-case web3-accounts)
|
||||
addr @address
|
||||
title "Select address"
|
||||
addr-not-in-web3? (and addr (as-> web3-accounts acc
|
||||
(map str/lower-case acc)
|
||||
(set acc)
|
||||
(contains? acc addr)
|
||||
(not acc)))
|
||||
items (cond->> web3-accounts
|
||||
addr-not-in-web3? (into [addr])
|
||||
(not addr) (into [title]))]
|
||||
[dropdown {:class "address-input"}
|
||||
title
|
||||
address
|
||||
items])
|
||||
[dropdown {:class "address-input"} "Select address"
|
||||
address
|
||||
(vec
|
||||
(for [acc web3-accounts]
|
||||
acc))]
|
||||
[:div.ui.input.address-input
|
||||
[input address {:placeholder "0x0000000000000000000000000000000000000000"
|
||||
[input address {:placeholder "0x0000000000000000000000000000000000000000"
|
||||
:auto-complete "off"
|
||||
:auto-correct "off"
|
||||
:spell-check "false"
|
||||
|
@ -413,6 +413,304 @@
|
||||
font-weight: 500;
|
||||
color: #42505c;
|
||||
}
|
||||
|
||||
.open-bounties-filter-and-sort {
|
||||
margin-top: 24px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.open-bounties-filter-and-sort {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.open-bounties-filter {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.open-bounties-filter-element {
|
||||
font-family: "PostGrotesk-Book";
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
line-height: 1.0;
|
||||
color: #8d99a4;
|
||||
//padding: 8px 12px;
|
||||
border-radius: 8px;
|
||||
border: solid 1px rgba(151, 151, 151, 0.2);
|
||||
margin-right: 10px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
|
||||
.text {
|
||||
margin: 8px 12px;
|
||||
}
|
||||
|
||||
.remove-container {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.remove {
|
||||
margin-left: -6px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.open-bounties-filter-element:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.open-bounties-filter-element-active
|
||||
{
|
||||
background-color: #57a7ed;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.open-bounties-filter-element-container:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.open-bounties-filter-element-tooltip {
|
||||
position: absolute;
|
||||
min-width: 227px;
|
||||
margin-top: 12px;
|
||||
border-radius: 10px;
|
||||
background-color: #ffffff;
|
||||
box-shadow: 0 15px 12px 0 rgba(161, 174, 182, 0.53), 0 0 38px 0 rgba(0, 0, 0, 0.05);
|
||||
font-family: "PostGrotesk-Book";
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
z-index: 999;
|
||||
max-height: 300px;
|
||||
overflow: scroll;
|
||||
|
||||
.open-bounties-filter-element-tooltip-value-input-container {
|
||||
display: flex;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.open-bounties-filter-element-tooltip-value-input-label {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.open-bounties-filter-element-tooltip-value-input {
|
||||
margin-top: 24px;
|
||||
width: 288.5px;
|
||||
height: 4px;
|
||||
background-color: #55a5ea;
|
||||
}
|
||||
|
||||
// generated with http://danielstern.ca/range.css/#/
|
||||
input[type=range] {
|
||||
-webkit-appearance: none;
|
||||
//width: 100%;
|
||||
margin: 14.5px 0;
|
||||
}
|
||||
input[type=range]:focus {
|
||||
outline: none;
|
||||
}
|
||||
input[type=range]::-webkit-slider-runnable-track {
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
cursor: pointer;
|
||||
box-shadow: 0px 0px 0px #000000, 0px 0px 0px #0d0d0d;
|
||||
background: #55a5ea;
|
||||
border-radius: 0px;
|
||||
border: 0px solid #010101;
|
||||
}
|
||||
input[type=range]::-webkit-slider-thumb {
|
||||
box-shadow: 0px 0px 0px #000000, 0px 0px 0px #0d0d0d;
|
||||
border: 0px solid #000000;
|
||||
height: 33px;
|
||||
width: 33px;
|
||||
border-radius: 50px;
|
||||
background: #55a5ea;
|
||||
cursor: pointer;
|
||||
-webkit-appearance: none;
|
||||
margin-top: -14.5px;
|
||||
}
|
||||
input[type=range]:focus::-webkit-slider-runnable-track {
|
||||
background: #5aa7eb;
|
||||
}
|
||||
input[type=range]::-moz-range-track {
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
cursor: pointer;
|
||||
box-shadow: 0px 0px 0px #000000, 0px 0px 0px #0d0d0d;
|
||||
background: #55a5ea;
|
||||
border-radius: 0px;
|
||||
border: 0px solid #010101;
|
||||
}
|
||||
input[type=range]::-moz-range-thumb {
|
||||
box-shadow: 0px 0px 0px #000000, 0px 0px 0px #0d0d0d;
|
||||
border: 0px solid #000000;
|
||||
height: 33px;
|
||||
width: 33px;
|
||||
border-radius: 50px;
|
||||
background: #55a5ea;
|
||||
cursor: pointer;
|
||||
}
|
||||
input[type=range]::-ms-track {
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
border-color: transparent;
|
||||
color: transparent;
|
||||
}
|
||||
input[type=range]::-ms-fill-lower {
|
||||
background: #50a3e9;
|
||||
border: 0px solid #010101;
|
||||
border-radius: 0px;
|
||||
box-shadow: 0px 0px 0px #000000, 0px 0px 0px #0d0d0d;
|
||||
}
|
||||
input[type=range]::-ms-fill-upper {
|
||||
background: #55a5ea;
|
||||
border: 0px solid #010101;
|
||||
border-radius: 0px;
|
||||
box-shadow: 0px 0px 0px #000000, 0px 0px 0px #0d0d0d;
|
||||
}
|
||||
input[type=range]::-ms-thumb {
|
||||
box-shadow: 0px 0px 0px #000000, 0px 0px 0px #0d0d0d;
|
||||
border: 0px solid #000000;
|
||||
height: 33px;
|
||||
width: 33px;
|
||||
border-radius: 50px;
|
||||
background: #55a5ea;
|
||||
cursor: pointer;
|
||||
height: 4px;
|
||||
}
|
||||
input[type=range]:focus::-ms-fill-lower {
|
||||
background: #55a5ea;
|
||||
}
|
||||
input[type=range]:focus::-ms-fill-upper {
|
||||
background: #5aa7eb;
|
||||
}
|
||||
|
||||
.open-bounties-filter-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
margin: 24px 16px;
|
||||
}
|
||||
|
||||
.open-bounties-filter-list-option {
|
||||
font-family: "PostGrotesk-Book";
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
line-height: 1.0;
|
||||
color: #42505c;
|
||||
padding: 8px 12px;
|
||||
border-radius: 8px;
|
||||
border: solid 1px rgba(151, 151, 151, 0.2);
|
||||
}
|
||||
|
||||
.open-bounties-filter-list-option.active {
|
||||
background-color: #57a7ed;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.open-bounties-filter-list-option:not(:first-child) {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.open-bounties-filter-list-option:hover {
|
||||
cursor: pointer;
|
||||
background-color: #57a7ed;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.open-bounties-filter-list-option-checkbox {
|
||||
label {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
label:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
label:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
input {
|
||||
background: green;
|
||||
transform: scale(1.3);
|
||||
}
|
||||
|
||||
input:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.text {
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
line-height: 1.0;
|
||||
margin-left: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.open-bounties-filter-list-option-checkbox:not(:first-child) {
|
||||
margin-top: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.open-bounties-sort {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.open-bounties-sort:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.open-bounties-sort-element {
|
||||
display: flex;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
line-height: 1.0;
|
||||
color: #8d99a4;
|
||||
padding: 8px 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.open-bounties-sort-element:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.open-bounties-sort-element-tooltip {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
//padding: 16px 0;
|
||||
min-width: 200px;
|
||||
border-radius: 8px;
|
||||
background-color: #1e3751;
|
||||
font-family: "PostGrotesk-Book";
|
||||
font-size: 15px;
|
||||
line-height: 1.0;
|
||||
text-align: left;
|
||||
color: #ffffff;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.open-bounties-sort-type {
|
||||
padding: 19px 24px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.open-bounties-sort-type:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.icon-forward-white-box {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.open-bounty-item {
|
||||
@ -496,7 +794,7 @@
|
||||
}
|
||||
|
||||
.open-claims-label {
|
||||
padding-left: 5px;
|
||||
padding-left: 15px;
|
||||
font-size: 15px;
|
||||
color: #57a7ed;
|
||||
}
|
||||
|
11
test/Jenkinsfile
vendored
Normal file
11
test/Jenkinsfile
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
node ('linux1') {sauce('1be1b688-e0e7-4314-92a0-db11f52d3c00') {
|
||||
checkout([$class: 'GitSCM', branches: [[name: '*/develop']], doGenerateSubmoduleConfigurations: false, extensions: [[$class: 'CleanBeforeCheckout']], submoduleCfg: [], userRemoteConfigs: [[url: 'https://github.com/status-im/open-bounty.git']]])
|
||||
configFileProvider([configFile(fileId: 'sob_automation_test_config', targetLocation: 'test/end-to-end/tests')]) {
|
||||
try {sh 'cd test/end-to-end/tests && python3 -m pytest -m sanity --build=$BUILD_NAME -v -n 1'
|
||||
}
|
||||
finally {
|
||||
saucePublisher()
|
||||
junit testDataPublishers: [[$class: 'SauceOnDemandReportPublisher', jobVisibility: 'public']], testResults: 'test/end-to-end/tests/*.xml' }
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user