Browse Source

merged iznik

Gleb Urvanov 4 years ago
parent
commit
e0793771b1
94 changed files with 1766 additions and 1285 deletions
  1. 7 12
      .travis.yml
  2. 29 236
      Cargo.lock
  3. 1 1
      Cargo.toml
  4. 6 0
      README.md
  5. 7 4
      node/Cargo.toml
  6. 64 19
      node/build.rs
  7. 79 80
      node/src/chain_spec.rs
  8. 14 14
      node/src/command.rs
  9. 1 0
      node/src/lib.rs
  10. 0 3
      node/src/node_rpc.rs
  11. 247 244
      node/src/service.rs
  12. 3 0
      package.json
  13. 0 3
      pioneer/.eslintignore
  14. 8 2
      pioneer/.eslintrc.js
  15. 3 1
      pioneer/packages/apps-config/src/api/spec/index.ts
  16. 3 0
      pioneer/packages/apps-config/src/api/spec/joystream-node.ts
  17. 5 0
      pioneer/packages/apps-config/src/settings/endpoints.ts
  18. 3 1
      pioneer/packages/apps-config/src/ui/logos/index.ts
  19. 1 0
      pioneer/packages/apps-config/src/ui/logos/nodes/joystream-node.svg
  20. 21 41
      pioneer/packages/apps-routing/src/index.ts
  21. 15 0
      pioneer/packages/apps-routing/src/joy-members.ts
  22. 27 0
      pioneer/packages/apps-routing/src/joy-pages.ts
  23. BIN
      pioneer/packages/apps/public/favicon.ico
  24. 11 0
      pioneer/packages/apps/public/index.html
  25. 14 1
      pioneer/packages/apps/src/Apps.tsx
  26. 1 1
      pioneer/packages/apps/src/Content/NotFound.tsx
  27. 2 6
      pioneer/packages/apps/src/Content/index.tsx
  28. 56 0
      pioneer/packages/apps/src/JoyTopBar/TopBar.tsx
  29. 1 1
      pioneer/packages/apps/src/SideBar/ChainInfo.tsx
  30. 3 24
      pioneer/packages/apps/src/SideBar/index.tsx
  31. 20 13
      pioneer/packages/apps/src/index.tsx
  32. 3 11
      pioneer/packages/apps/webpack.base.config.js
  33. 2 1
      pioneer/packages/apps/webpack.config.js
  34. 0 0
      pioneer/packages/joy-members/.skip-build
  35. 3 3
      pioneer/packages/joy-members/package.json
  36. 47 29
      pioneer/packages/joy-members/src/Dashboard.tsx
  37. 27 23
      pioneer/packages/joy-members/src/Details.tsx
  38. 3 1
      pioneer/packages/joy-members/src/DetailsByHandle.tsx
  39. 53 45
      pioneer/packages/joy-members/src/EditForm.tsx
  40. 11 6
      pioneer/packages/joy-members/src/List.tsx
  41. 8 8
      pioneer/packages/joy-members/src/MemberPreview.tsx
  42. 0 58
      pioneer/packages/joy-members/src/index.css
  43. 16 11
      pioneer/packages/joy-members/src/index.tsx
  44. 62 0
      pioneer/packages/joy-members/src/style.ts
  45. 3 2
      pioneer/packages/joy-members/src/utils.ts
  46. 0 0
      pioneer/packages/joy-pages/.skip-build
  47. 3 3
      pioneer/packages/joy-pages/package.json
  48. 4 2
      pioneer/packages/joy-pages/src/index.tsx
  49. 0 14
      pioneer/packages/joy-utils-old/src/functions/misc.ts
  50. 0 1
      pioneer/packages/joy-utils-old/src/react/components/index.tsx
  51. 0 1
      pioneer/packages/joy-utils-old/src/react/context/index.tsx
  52. 0 0
      pioneer/packages/joy-utils/README.md
  53. 3 3
      pioneer/packages/joy-utils/package.json
  54. 0 0
      pioneer/packages/joy-utils/src/functions/date.ts
  55. 2 2
      pioneer/packages/joy-utils/src/functions/format.ts
  56. 167 0
      pioneer/packages/joy-utils/src/functions/misc.ts
  57. 1 1
      pioneer/packages/joy-utils/src/react/components/FlexCenter.tsx
  58. 4 1
      pioneer/packages/joy-utils/src/react/components/MutedText.tsx
  59. 3 0
      pioneer/packages/joy-utils/src/react/components/Section.tsx
  60. 46 50
      pioneer/packages/joy-utils/src/react/components/TxButton.tsx
  61. 5 5
      pioneer/packages/joy-utils/src/react/components/forms.tsx
  62. 4 0
      pioneer/packages/joy-utils/src/react/components/index.tsx
  63. 30 10
      pioneer/packages/joy-utils/src/react/context/account.tsx
  64. 2 0
      pioneer/packages/joy-utils/src/react/context/index.tsx
  65. 2 6
      pioneer/packages/joy-utils/src/react/context/membership.tsx
  66. 0 0
      pioneer/packages/joy-utils/src/react/helpers/index.ts
  67. 124 0
      pioneer/packages/joy-utils/src/react/hocs/accounts.tsx
  68. 104 0
      pioneer/packages/joy-utils/src/react/hocs/guards.tsx
  69. 2 0
      pioneer/packages/joy-utils/src/react/hooks/index.ts
  70. 6 0
      pioneer/packages/joy-utils/src/react/hooks/useMyAccount.tsx
  71. 6 0
      pioneer/packages/joy-utils/src/react/hooks/useMyMembership.tsx
  72. 0 28
      pioneer/packages/old-apps/apps-routing/src/joy-pages.ts
  73. 0 17
      pioneer/packages/old-apps/apps/src/TopBar.css
  74. 0 47
      pioneer/packages/old-apps/apps/src/TopBar.tsx
  75. 0 11
      pioneer/packages/old-apps/react-components/src/styles/old-theme.ts-unused
  76. 1 1
      pioneer/packages/page-staking/src/Targets/Summary.tsx
  77. 8 0
      pioneer/packages/react-components/src/InputAddress/index.tsx
  78. 5 2
      pioneer/packages/react-components/src/Tabs/Tab.tsx
  79. 1 0
      pioneer/packages/react-components/src/Tabs/types.ts
  80. 3 1
      pioneer/packages/react-components/src/styles/index.ts
  81. 16 8
      pioneer/packages/react-components/src/styles/joystream.ts
  82. 1 1
      pioneer/packages/react-components/src/styles/theme.ts
  83. 7 9
      pioneer/tsconfig.json
  84. 1 3
      runtime-modules/proposals/engine/Cargo.toml
  85. 9 9
      runtime-modules/service-discovery/src/lib.rs
  86. 3 3
      runtime-modules/service-discovery/src/tests.rs
  87. 0 6
      runtime/Cargo.toml
  88. 4 10
      runtime/build.rs
  89. 0 20
      runtime/src/lib.rs
  90. 1 39
      runtime/src/runtime_api.rs
  91. 1 1
      runtime/src/tests/storage_integration.rs
  92. 10 23
      utils/chain-spec-builder/Cargo.toml
  93. 15 24
      utils/chain-spec-builder/src/main.rs
  94. 272 18
      yarn.lock

+ 7 - 12
.travis.yml

@@ -7,15 +7,9 @@ language: rust
 # sometimes break the build. When cache is enabled do not use the produced WASM build.
 # This also means the binary should not be used to produce the final chainspec file (because the same
 # one is embedded in the binary)
-cache: cargo
+# cache: cargo
 
-rust:
-  - stable
-
-matrix:
-  include:
-    - os: linux
-      env: TARGET=x86_64-unknown-linux-gnu
+rust: stable
 
 # Skip Rust build in a pull request if no rust project files were modified
 before_install:
@@ -41,7 +35,8 @@ before_script:
 
 script:
   # we set release as build type for all steps to benefit from already compiled packages in prior steps
-  - BUILD_DUMMY_WASM_BINARY=1 cargo clippy --release --target=${TARGET} -- -D warnings
-  - BUILD_DUMMY_WASM_BINARY=1 cargo test --release --verbose --all --target=${TARGET}
-  - TRIGGER_WASM_BUILD=1 WASM_BUILD_TOOLCHAIN=nightly-2020-05-23 cargo build --release --target=${TARGET} -p joystream-node
-  - ls -l ./target/${TARGET}/release/wbuild/joystream-node-runtime/
+  - BUILD_DUMMY_WASM_BINARY=1 cargo clippy --release -- -D warnings
+  - BUILD_DUMMY_WASM_BINARY=1 cargo test --release --verbose --all
+  - TRIGGER_WASM_BUILD=1 WASM_BUILD_TOOLCHAIN=nightly-2020-05-23 cargo build --release -p joystream-node
+  - ls -l ./target/release/wbuild/joystream-node-runtime/
+  - ./target/release/joystream-node --version

+ 29 - 236
Cargo.lock

@@ -560,6 +560,20 @@ dependencies = [
  "zeroize",
 ]
 
+[[package]]
+name = "chain-spec-builder"
+version = "3.0.0"
+dependencies = [
+ "ansi_term 0.12.1",
+ "joystream-node",
+ "rand 0.7.3",
+ "sc-chain-spec",
+ "sc-keystore",
+ "sc-telemetry",
+ "sp-core",
+ "structopt",
+]
+
 [[package]]
 name = "chrono"
 version = "0.4.11"
@@ -1963,8 +1977,6 @@ dependencies = [
  "joystream-node-runtime",
  "jsonrpc-core",
  "node-inspect",
- "pallet-contracts",
- "pallet-contracts-rpc",
  "pallet-grandpa",
  "pallet-im-online",
  "pallet-transaction-payment",
@@ -1986,7 +1998,6 @@ dependencies = [
  "sc-network",
  "sc-rpc-api",
  "sc-service",
- "sc-service-test",
  "sc-transaction-pool",
  "serde",
  "serde_json",
@@ -2006,9 +2017,9 @@ dependencies = [
  "sp-transaction-pool",
  "structopt",
  "substrate-browser-utils",
+ "substrate-build-script-utils",
  "substrate-frame-rpc-system",
  "tempfile",
- "vergen",
  "wasm-bindgen",
  "wasm-bindgen-futures",
 ]
@@ -2030,9 +2041,6 @@ dependencies = [
  "pallet-collective",
  "pallet-common",
  "pallet-content-working-group",
- "pallet-contracts",
- "pallet-contracts-primitives",
- "pallet-contracts-rpc-runtime-api",
  "pallet-finality-tracker",
  "pallet-forum",
  "pallet-governance",
@@ -3291,67 +3299,6 @@ dependencies = [
  "sp-std",
 ]
 
-[[package]]
-name = "pallet-contracts"
-version = "2.0.0-rc4"
-source = "git+https://github.com/paritytech/substrate.git?rev=00768a1f21a579c478fe5d4f51e1fa71f7db9fd4#00768a1f21a579c478fe5d4f51e1fa71f7db9fd4"
-dependencies = [
- "frame-support",
- "frame-system",
- "pallet-contracts-primitives",
- "parity-scale-codec",
- "parity-wasm",
- "pwasm-utils",
- "serde",
- "sp-core",
- "sp-io",
- "sp-runtime",
- "sp-sandbox",
- "sp-std",
- "wasmi-validation",
-]
-
-[[package]]
-name = "pallet-contracts-primitives"
-version = "2.0.0-rc4"
-source = "git+https://github.com/paritytech/substrate.git?rev=00768a1f21a579c478fe5d4f51e1fa71f7db9fd4#00768a1f21a579c478fe5d4f51e1fa71f7db9fd4"
-dependencies = [
- "parity-scale-codec",
- "sp-runtime",
- "sp-std",
-]
-
-[[package]]
-name = "pallet-contracts-rpc"
-version = "0.8.0-rc4"
-source = "git+https://github.com/paritytech/substrate.git?rev=00768a1f21a579c478fe5d4f51e1fa71f7db9fd4#00768a1f21a579c478fe5d4f51e1fa71f7db9fd4"
-dependencies = [
- "jsonrpc-core",
- "jsonrpc-core-client",
- "jsonrpc-derive",
- "pallet-contracts-primitives",
- "pallet-contracts-rpc-runtime-api",
- "parity-scale-codec",
- "serde",
- "sp-api",
- "sp-blockchain",
- "sp-core",
- "sp-rpc",
- "sp-runtime",
-]
-
-[[package]]
-name = "pallet-contracts-rpc-runtime-api"
-version = "0.8.0-rc4"
-source = "git+https://github.com/paritytech/substrate.git?rev=00768a1f21a579c478fe5d4f51e1fa71f7db9fd4#00768a1f21a579c478fe5d4f51e1fa71f7db9fd4"
-dependencies = [
- "pallet-contracts-primitives",
- "parity-scale-codec",
- "sp-api",
- "sp-runtime",
- "sp-std",
-]
-
 [[package]]
 name = "pallet-finality-tracker"
 version = "2.0.0-rc4"
@@ -4207,6 +4154,12 @@ version = "0.3.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d36492546b6af1463394d46f0c834346f31548646f6ba10849802c9c9a27ac33"
 
+[[package]]
+name = "platforms"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "feb3b2b1033b8a60b4da6ee470325f887758c95d5320f52f9ce0df055a55940e"
+
 [[package]]
 name = "poly1305"
 version = "0.6.0"
@@ -4415,17 +4368,6 @@ version = "2.16.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d883f78645c21b7281d21305181aa1f4dd9e9363e7cf2566c93121552cff003e"
 
-[[package]]
-name = "pwasm-utils"
-version = "0.12.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4f7a12f176deee919f4ba55326ee17491c8b707d0987aed822682c821b660192"
-dependencies = [
- "byteorder",
- "log",
- "parity-wasm",
-]
-
 [[package]]
 name = "quick-error"
 version = "1.2.3"
@@ -5658,43 +5600,6 @@ dependencies = [
  "wasm-timer",
 ]
 
-[[package]]
-name = "sc-service-test"
-version = "2.0.0-rc4"
-source = "git+https://github.com/paritytech/substrate.git?rev=00768a1f21a579c478fe5d4f51e1fa71f7db9fd4#00768a1f21a579c478fe5d4f51e1fa71f7db9fd4"
-dependencies = [
- "env_logger",
- "fdlimit",
- "futures 0.1.29",
- "futures 0.3.4",
- "hex-literal",
- "log",
- "parity-scale-codec",
- "parking_lot 0.10.2",
- "sc-block-builder",
- "sc-client-api",
- "sc-client-db",
- "sc-executor",
- "sc-light",
- "sc-network",
- "sc-service",
- "sp-api",
- "sp-blockchain",
- "sp-consensus",
- "sp-core",
- "sp-externalities",
- "sp-panic-handler",
- "sp-runtime",
- "sp-state-machine",
- "sp-storage",
- "sp-transaction-pool",
- "sp-trie",
- "substrate-test-runtime",
- "substrate-test-runtime-client",
- "tempfile",
- "tokio 0.1.22",
-]
-
 [[package]]
 name = "sc-state-db"
 version = "0.8.0-rc4"
@@ -6260,20 +6165,6 @@ dependencies = [
  "wasm-timer",
 ]
 
-[[package]]
-name = "sp-consensus-aura"
-version = "0.8.0-rc4"
-source = "git+https://github.com/paritytech/substrate.git?rev=00768a1f21a579c478fe5d4f51e1fa71f7db9fd4#00768a1f21a579c478fe5d4f51e1fa71f7db9fd4"
-dependencies = [
- "parity-scale-codec",
- "sp-api",
- "sp-application-crypto",
- "sp-inherents",
- "sp-runtime",
- "sp-std",
- "sp-timestamp",
-]
-
 [[package]]
 name = "sp-consensus-babe"
 version = "0.8.0-rc4"
@@ -6546,19 +6437,6 @@ dependencies = [
  "syn 1.0.17",
 ]
 
-[[package]]
-name = "sp-sandbox"
-version = "0.8.0-rc4"
-source = "git+https://github.com/paritytech/substrate.git?rev=00768a1f21a579c478fe5d4f51e1fa71f7db9fd4#00768a1f21a579c478fe5d4f51e1fa71f7db9fd4"
-dependencies = [
- "parity-scale-codec",
- "sp-core",
- "sp-io",
- "sp-std",
- "sp-wasm-interface",
- "wasmi",
-]
-
 [[package]]
 name = "sp-serializer"
 version = "2.0.0-rc4"
@@ -6853,6 +6731,14 @@ dependencies = [
  "wasm-bindgen-futures",
 ]
 
+[[package]]
+name = "substrate-build-script-utils"
+version = "2.0.0-rc4"
+source = "git+https://github.com/paritytech/substrate.git?rev=00768a1f21a579c478fe5d4f51e1fa71f7db9fd4#00768a1f21a579c478fe5d4f51e1fa71f7db9fd4"
+dependencies = [
+ "platforms",
+]
+
 [[package]]
 name = "substrate-frame-rpc-system"
 version = "2.0.0-rc4"
@@ -6890,89 +6776,6 @@ dependencies = [
  "tokio 0.2.22",
 ]
 
-[[package]]
-name = "substrate-test-client"
-version = "2.0.0-rc4"
-source = "git+https://github.com/paritytech/substrate.git?rev=00768a1f21a579c478fe5d4f51e1fa71f7db9fd4#00768a1f21a579c478fe5d4f51e1fa71f7db9fd4"
-dependencies = [
- "futures 0.3.4",
- "hash-db",
- "parity-scale-codec",
- "sc-client-api",
- "sc-client-db",
- "sc-consensus",
- "sc-executor",
- "sc-light",
- "sc-service",
- "sp-blockchain",
- "sp-consensus",
- "sp-core",
- "sp-keyring",
- "sp-runtime",
- "sp-state-machine",
-]
-
-[[package]]
-name = "substrate-test-runtime"
-version = "2.0.0-rc4"
-source = "git+https://github.com/paritytech/substrate.git?rev=00768a1f21a579c478fe5d4f51e1fa71f7db9fd4#00768a1f21a579c478fe5d4f51e1fa71f7db9fd4"
-dependencies = [
- "cfg-if",
- "frame-executive",
- "frame-support",
- "frame-system",
- "frame-system-rpc-runtime-api",
- "log",
- "memory-db",
- "pallet-babe",
- "pallet-timestamp",
- "parity-scale-codec",
- "parity-util-mem",
- "sc-service",
- "serde",
- "sp-api",
- "sp-application-crypto",
- "sp-block-builder",
- "sp-consensus-aura",
- "sp-consensus-babe",
- "sp-core",
- "sp-finality-grandpa",
- "sp-inherents",
- "sp-io",
- "sp-keyring",
- "sp-offchain",
- "sp-runtime",
- "sp-runtime-interface",
- "sp-session",
- "sp-std",
- "sp-transaction-pool",
- "sp-trie",
- "sp-version",
- "substrate-wasm-builder-runner",
- "trie-db",
-]
-
-[[package]]
-name = "substrate-test-runtime-client"
-version = "2.0.0-rc4"
-source = "git+https://github.com/paritytech/substrate.git?rev=00768a1f21a579c478fe5d4f51e1fa71f7db9fd4#00768a1f21a579c478fe5d4f51e1fa71f7db9fd4"
-dependencies = [
- "futures 0.3.4",
- "parity-scale-codec",
- "sc-block-builder",
- "sc-client-api",
- "sc-consensus",
- "sc-light",
- "sc-service",
- "sp-api",
- "sp-blockchain",
- "sp-consensus",
- "sp-core",
- "sp-runtime",
- "substrate-test-client",
- "substrate-test-runtime",
-]
-
 [[package]]
 name = "substrate-wasm-builder-runner"
 version = "1.0.6"
@@ -7687,16 +7490,6 @@ version = "0.8.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
 
-[[package]]
-name = "vergen"
-version = "3.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4ce50d8996df1f85af15f2cd8d33daae6e479575123ef4314a51a70a230739cb"
-dependencies = [
- "bitflags",
- "chrono",
-]
-
 [[package]]
 name = "version_check"
 version = "0.9.2"

+ 1 - 1
Cargo.toml

@@ -20,7 +20,7 @@ members = [
 	"runtime-modules/versioned-store-permissions",
 	"runtime-modules/working-group",
 	"node",
-#	"utils/chain-spec-builder/"
+	"utils/chain-spec-builder/"
 ]
 
 [profile.release]

+ 6 - 0
README.md

@@ -123,6 +123,12 @@ During a rebase/merge you may want to skip all hooks, you can use `HUSKY_SKIP_HO
 HUSKY_SKIP_HOOKS=1 git rebase ...
 ```
 
+## RLS Extension in VScode or Atom Editors
+
+If you use RLS extension in your IDE, start your editor with the `BUILD_DUMMY_WASM_BINARY=1` environment set to workaround a build issue that occurs in the IDE only.
+
+`BUILD_DUMMY_WASM_BINARY=1 code ./joystream`
+
 ## Authors
 
 See the list of [contributors](https://github.com/Joystream/joystream/graphs/contributors) who participated in this project.

+ 7 - 4
node/Cargo.toml

@@ -54,9 +54,7 @@ sc-rpc-api = { package = 'sc-rpc-api', git = 'https://github.com/paritytech/subs
 sc-executor = { package = 'sc-executor', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
 
 # frame dependencies
-pallet-contracts = { package = 'pallet-contracts', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
 pallet-im-online = { package = 'pallet-im-online', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
-pallet-contracts-rpc = { package = 'pallet-contracts-rpc', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
 pallet-transaction-payment-rpc = { package = 'pallet-transaction-payment-rpc', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
 substrate-frame-rpc-system = { package = 'substrate-frame-rpc-system', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
 frame-benchmarking = { package = 'frame-benchmarking', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
@@ -80,13 +78,17 @@ codec = { package = "parity-scale-codec", version = "1.3.1" }
 sp-timestamp = { package = 'sp-timestamp', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
 sp-keyring = { package = 'sp-keyring', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
 sc-consensus-babe = { git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4', features = ["test-helpers"]}
-sc-service-test = { git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
+# sc-service-test = { git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
 frame-system = { package = 'frame-system', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
 pallet-transaction-payment = { package = 'pallet-transaction-payment', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
 pallet-grandpa = { package = 'pallet-grandpa', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
 
 [build-dependencies]
-vergen = '3'
+structopt = { version = "0.3.8", optional = true }
+node-inspect = { git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4', optional = true}
+sc-cli = { git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4', optional = true}
+frame-benchmarking-cli = { git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4', optional = true }
+substrate-build-script-utils = { git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4', optional = true }
 
 [features]
 default = [ "cli" ]
@@ -101,6 +103,7 @@ cli = [
 	"frame-benchmarking-cli",
 	"sc-service/db",
 	"structopt",
+	"substrate-build-script-utils",
 ]
 runtime-benchmarks = [
 	"node-runtime/runtime-benchmarks",

+ 64 - 19
node/build.rs

@@ -1,27 +1,72 @@
-use std::{env, path::PathBuf};
+// This file is part of Substrate.
 
-use vergen::{generate_cargo_keys, ConstantsFlags};
+// Copyright (C) 2017-2020 Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
 
-const ERROR_MSG: &str = "Failed to generate metadata files";
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
 
 fn main() {
-    generate_cargo_keys(ConstantsFlags::SHA_SHORT).expect(ERROR_MSG);
-
-    let mut manifest_dir = PathBuf::from(
-        env::var("CARGO_MANIFEST_DIR").expect("`CARGO_MANIFEST_DIR` is always set by cargo."),
-    );
-
-    while manifest_dir.parent().is_some() {
-        if manifest_dir.join(".git/HEAD").exists() {
-            println!(
-                "cargo:rerun-if-changed={}",
-                manifest_dir.join(".git/HEAD").display()
-            );
-            return;
-        }
+    #[cfg(feature = "cli")]
+    cli::main();
+}
+
+#[cfg(feature = "cli")]
+mod cli {
+    include!("src/cli.rs");
+
+    use sc_cli::structopt::clap::Shell;
+    use std::{env, fs, path::Path};
+    use substrate_build_script_utils::{generate_cargo_keys, rerun_if_git_head_changed};
+
+    pub fn main() {
+        build_shell_completion();
+        generate_cargo_keys();
 
-        manifest_dir.pop();
+        rerun_if_git_head_changed();
     }
 
-    println!("cargo:warning=Could not find `.git/HEAD` from manifest dir!");
+    /// Build shell completion scripts for all known shells
+    /// Full list in https://github.com/kbknapp/clap-rs/blob/e9d0562a1dc5dfe731ed7c767e6cee0af08f0cf9/src/app/parser.rs#L123
+    fn build_shell_completion() {
+        for shell in &[
+            Shell::Bash,
+            Shell::Fish,
+            Shell::Zsh,
+            Shell::Elvish,
+            Shell::PowerShell,
+        ] {
+            build_completion(shell);
+        }
+    }
+
+    /// Build the shell auto-completion for a given Shell
+    fn build_completion(shell: &Shell) {
+        let outdir = match env::var_os("OUT_DIR") {
+            None => return,
+            Some(dir) => dir,
+        };
+        let path = Path::new(&outdir)
+            .parent()
+            .unwrap()
+            .parent()
+            .unwrap()
+            .parent()
+            .unwrap()
+            .join("completion-scripts");
+
+        fs::create_dir(&path).ok();
+
+        Cli::clap().gen_completions("joystream-node", *shell, &path);
+    }
 }

+ 79 - 80
node/src/chain_spec.rs

@@ -19,7 +19,6 @@
 // Example:  voting_period: 1 * DAY
 #![allow(clippy::identity_op)]
 
-use node_runtime::{AccountId, GenesisConfig};
 use pallet_im_online::sr25519::AuthorityId as ImOnlineId;
 use serde_json as json;
 use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId;
@@ -32,12 +31,14 @@ use sp_runtime::Perbill;
 use node_runtime::{
     versioned_store::InputValidationLengthConstraint as VsInputValidation,
     AuthorityDiscoveryConfig, BabeConfig, Balance, BalancesConfig, ContentWorkingGroupConfig,
-    ContractsConfig, CouncilConfig, CouncilElectionConfig, DataObjectStorageRegistryConfig,
+    CouncilConfig, CouncilElectionConfig, DataObjectStorageRegistryConfig,
     DataObjectTypeRegistryConfig, ElectionParameters, GrandpaConfig, ImOnlineConfig, MembersConfig,
     ProposalsCodexConfig, SessionConfig, SessionKeys, Signature, StakerStatus, StakingConfig,
     StorageWorkingGroupConfig, SudoConfig, SystemConfig, VersionedStoreConfig, DAYS, WASM_BINARY,
 };
 
+pub use node_runtime::{AccountId, GenesisConfig};
+
 type AccountPublic = <Signature as Verify>::Signer;
 
 /// Specialized `ChainSpec`. This is a specialization of the general Substrate ChainSpec type.
@@ -125,6 +126,7 @@ impl Alternative {
                             get_account_id_from_seed::<sr25519::Public>("Alice//stash"),
                             get_account_id_from_seed::<sr25519::Public>("Bob//stash"),
                         ],
+                        crate::proposals_config::development(),
                     )
                 },
                 Vec::new(),
@@ -158,6 +160,7 @@ impl Alternative {
                             get_account_id_from_seed::<sr25519::Public>("Eve//stash"),
                             get_account_id_from_seed::<sr25519::Public>("Ferdie//stash"),
                         ],
+                        crate::proposals_config::development(),
                     )
                 },
                 Vec::new(),
@@ -198,16 +201,13 @@ pub fn testnet_genesis(
     )>,
     root_key: AccountId,
     endowed_accounts: Vec<AccountId>,
+    cpcp: node_runtime::ProposalsConfigParameters,
 ) -> GenesisConfig {
     const CENTS: Balance = 1;
     const DOLLARS: Balance = 100 * CENTS;
     const STASH: Balance = 20 * DOLLARS;
     const ENDOWMENT: Balance = 100_000 * DOLLARS;
 
-    let enable_println = false;
-
-    // default codex proposals config parameters
-    let cpcp = node_runtime::ProposalsConfigParameters::default();
     let default_text_constraint = node_runtime::working_group::default_text_constraint();
 
     GenesisConfig {
@@ -234,7 +234,9 @@ pub fn testnet_genesis(
             slash_reward_fraction: Perbill::from_percent(10),
             ..Default::default()
         }),
-        pallet_sudo: Some(SudoConfig { key: root_key }),
+        pallet_sudo: Some(SudoConfig {
+            key: root_key.clone(),
+        }),
         pallet_babe: Some(BabeConfig {
             authorities: vec![],
         }),
@@ -255,12 +257,6 @@ pub fn testnet_genesis(
                 })
                 .collect::<Vec<_>>(),
         }),
-        pallet_contracts: Some(ContractsConfig {
-            current_schedule: pallet_contracts::Schedule {
-                enable_println, // this should only be enabled on development chains
-                ..Default::default()
-            },
-        }),
         council: Some(CouncilConfig {
             active_council: vec![],
             term_ends_at: 1,
@@ -282,9 +278,7 @@ pub fn testnet_genesis(
             default_paid_membership_fee: 100u128,
             members: vec![],
         }),
-        forum: Some(crate::forum_config::from_serialized::create(
-            endowed_accounts[0].clone(),
-        )),
+        forum: Some(crate::forum_config::from_serialized::create(root_key)),
         data_object_type_registry: Some(DataObjectTypeRegistryConfig {
             first_data_object_type_id: 1,
         }),
@@ -350,7 +344,7 @@ pub fn testnet_genesis(
             set_content_working_group_mint_capacity_proposal_grace_period: cpcp
                 .set_content_working_group_mint_capacity_proposal_grace_period,
             set_lead_proposal_voting_period: cpcp.set_lead_proposal_voting_period,
-            set_lead_proposal_grace_period: cpcp.set_lead_proposal_voting_period,
+            set_lead_proposal_grace_period: cpcp.set_lead_proposal_grace_period,
             spending_proposal_voting_period: cpcp.spending_proposal_voting_period,
             spending_proposal_grace_period: cpcp.spending_proposal_grace_period,
             add_working_group_opening_proposal_voting_period: cpcp
@@ -389,71 +383,76 @@ pub fn testnet_genesis(
     }
 }
 
-#[cfg(test)]
-pub(crate) mod tests {
-    use super::*;
-    use crate::service::{new_full, new_light};
-    use sc_service_test;
+// Tests are commented out until we find a solution to why
+// building dependencies for the tests are taking so long on Travis CI
 
-    fn local_testnet_genesis_instant_single() -> GenesisConfig {
-        testnet_genesis(
-            vec![get_authority_keys_from_seed("Alice")],
-            get_account_id_from_seed::<sr25519::Public>("Alice"),
-            vec![get_authority_keys_from_seed("Alice").0],
-        )
-    }
+// #[cfg(test)]
+// pub(crate) mod tests {
+//     use super::*;
+//     use crate::service::{new_full, new_light};
+//     use sc_service_test;
 
-    /// Local testnet config (single validator - Alice)
-    pub fn integration_test_config_with_single_authority() -> ChainSpec {
-        ChainSpec::from_genesis(
-            "Integration Test",
-            "test",
-            ChainType::Development,
-            local_testnet_genesis_instant_single,
-            vec![],
-            None,
-            None,
-            None,
-            Default::default(),
-        )
-    }
+//     fn local_testnet_genesis_instant_single() -> GenesisConfig {
+//         testnet_genesis(
+//             vec![get_authority_keys_from_seed("Alice")],
+//             get_account_id_from_seed::<sr25519::Public>("Alice"),
+//             vec![get_authority_keys_from_seed("Alice").0],
+//             crate::proposals_config::development(),
+//         )
+//     }
 
-    fn local_testnet_genesis() -> GenesisConfig {
-        testnet_genesis(
-            vec![
-                get_authority_keys_from_seed("Alice"),
-                get_authority_keys_from_seed("Bob"),
-            ],
-            get_account_id_from_seed::<sr25519::Public>("Alice"),
-            vec![
-                get_authority_keys_from_seed("Alice").0,
-                get_authority_keys_from_seed("Bob").0,
-            ],
-        )
-    }
+//     /// Local testnet config (single validator - Alice)
+//     pub fn integration_test_config_with_single_authority() -> ChainSpec {
+//         ChainSpec::from_genesis(
+//             "Integration Test",
+//             "test",
+//             ChainType::Development,
+//             local_testnet_genesis_instant_single,
+//             vec![],
+//             None,
+//             None,
+//             None,
+//             Default::default(),
+//         )
+//     }
 
-    /// Local testnet config (multivalidator Alice + Bob)
-    pub fn integration_test_config_with_two_authorities() -> ChainSpec {
-        ChainSpec::from_genesis(
-            "Integration Test",
-            "test",
-            ChainType::Development,
-            local_testnet_genesis,
-            vec![],
-            None,
-            None,
-            None,
-            Default::default(),
-        )
-    }
+//     fn local_testnet_genesis() -> GenesisConfig {
+//         testnet_genesis(
+//             vec![
+//                 get_authority_keys_from_seed("Alice"),
+//                 get_authority_keys_from_seed("Bob"),
+//             ],
+//             get_account_id_from_seed::<sr25519::Public>("Alice"),
+//             vec![
+//                 get_authority_keys_from_seed("Alice").0,
+//                 get_authority_keys_from_seed("Bob").0,
+//             ],
+//             crate::proposals_config::development(),
+//         )
+//     }
 
-    #[test]
-    #[ignore]
-    fn test_connectivity() {
-        sc_service_test::connectivity(
-            integration_test_config_with_two_authorities(),
-            |config| new_full(config),
-            |config| new_light(config),
-        );
-    }
-}
+//     /// Local testnet config (multivalidator Alice + Bob)
+//     pub fn integration_test_config_with_two_authorities() -> ChainSpec {
+//         ChainSpec::from_genesis(
+//             "Integration Test",
+//             "test",
+//             ChainType::Development,
+//             local_testnet_genesis,
+//             vec![],
+//             None,
+//             None,
+//             None,
+//             Default::default(),
+//         )
+//     }
+
+//     #[test]
+//     #[ignore]
+//     fn test_connectivity() {
+//         sc_service_test::connectivity(
+//             integration_test_config_with_two_authorities(),
+//             |config| new_full(config),
+//             |config| new_light(config),
+//         );
+//     }
+// }

+ 14 - 14
node/src/command.rs

@@ -29,18 +29,6 @@ impl SubstrateCli for Cli {
         "Joystream Node"
     }
 
-    fn impl_version() -> &'static str {
-        "3.0.0"
-    }
-
-    fn description() -> &'static str {
-        "Joystream substrate node"
-    }
-
-    fn author() -> &'static str {
-        "Joystream contributors"
-    }
-
     fn support_url() -> &'static str {
         "https://www.joystream.org/"
     }
@@ -53,10 +41,22 @@ impl SubstrateCli for Cli {
         "joystream-node"
     }
 
+    fn impl_version() -> &'static str {
+        env!("SUBSTRATE_CLI_IMPL_VERSION")
+    }
+
+    fn description() -> &'static str {
+        env!("CARGO_PKG_DESCRIPTION")
+    }
+
+    fn author() -> &'static str {
+        env!("CARGO_PKG_AUTHORS")
+    }
+
     fn load_spec(&self, id: &str) -> std::result::Result<Box<dyn sc_service::ChainSpec>, String> {
         Ok(match id {
-            "dev" => Box::new(chain_spec::Alternative::Development.load().unwrap()), //TODO
-            "local" => Box::new(chain_spec::Alternative::LocalTestnet.load().unwrap()),
+            "dev" => Box::new(chain_spec::Alternative::Development.load()?),
+            "local" => Box::new(chain_spec::Alternative::LocalTestnet.load()?),
             path => Box::new(chain_spec::ChainSpec::from_json_file(
                 std::path::PathBuf::from(path),
             )?),

+ 1 - 0
node/src/lib.rs

@@ -2,6 +2,7 @@ pub mod chain_spec;
 pub mod cli;
 pub mod forum_config;
 pub mod members_config;
+pub mod proposals_config;
 #[macro_use]
 pub mod service;
 pub mod command;

+ 0 - 3
node/src/node_rpc.rs

@@ -101,7 +101,6 @@ where
     C: HeaderBackend<Block> + HeaderMetadata<Block, Error = BlockChainError> + 'static,
     C: Send + Sync + 'static,
     C::Api: substrate_frame_rpc_system::AccountNonceApi<Block, AccountId, Index>,
-    C::Api: pallet_contracts_rpc::ContractsRuntimeApi<Block, AccountId, Balance, BlockNumber>,
     C::Api: pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi<
         Block,
         Balance,
@@ -113,7 +112,6 @@ where
     M: jsonrpc_core::Metadata + Default,
     SC: SelectChain<Block> + 'static,
 {
-    use pallet_contracts_rpc::{Contracts, ContractsApi};
     use pallet_transaction_payment_rpc::{TransactionPayment, TransactionPaymentApi};
     use substrate_frame_rpc_system::{FullSystem, SystemApi};
 
@@ -144,7 +142,6 @@ where
     // Making synchronous calls in light client freezes the browser currently,
     // more context: https://github.com/paritytech/substrate/pull/3480
     // These RPCs should use an asynchronous caller instead.
-    io.extend_with(ContractsApi::to_delegate(Contracts::new(client.clone())));
     io.extend_with(TransactionPaymentApi::to_delegate(TransactionPayment::new(
         client.clone(),
     )));

+ 247 - 244
node/src/service.rs

@@ -415,247 +415,250 @@ pub fn new_light(config: Configuration) -> Result<impl AbstractService, ServiceE
     Ok(service)
 }
 
-#[cfg(test)]
-mod tests {
-    use crate::node_executor;
-    use crate::node_rpc;
-    use crate::service::{new_full, new_light};
-    use codec::{Decode, Encode};
-    use node_runtime::RuntimeApi;
-    use node_runtime::{currency::CENTS, SLOT_DURATION};
-    use node_runtime::{opaque::Block, AccountId, DigestItem, Signature};
-    use node_runtime::{BalancesCall, Call, UncheckedExtrinsic};
-    use sc_consensus_babe::{BabeIntermediate, CompatibleDigestItem, INTERMEDIATE_KEY};
-    use sc_consensus_epochs::descendent_query;
-    use sc_finality_grandpa::{self as grandpa};
-    use sc_service::AbstractService;
-    use sp_consensus::{
-        BlockImport, BlockImportParams, BlockOrigin, Environment, ForkChoiceStrategy, Proposer,
-        RecordProof,
-    };
-    use sp_core::{crypto::Pair as CryptoPair, H256};
-    use sp_finality_tracker;
-    use sp_keyring::AccountKeyring;
-    use sp_runtime::traits::IdentifyAccount;
-    use sp_runtime::{
-        generic::{BlockId, Digest, Era, SignedPayload},
-        traits::Verify,
-        traits::{Block as BlockT, Header as HeaderT},
-        OpaqueExtrinsic,
-    };
-    use sp_timestamp;
-    use sp_transaction_pool::{ChainEvent, MaintainedTransactionPool};
-    use std::{any::Any, borrow::Cow, sync::Arc};
-
-    type AccountPublic = <Signature as Verify>::Signer;
-
-    // Long running test. Run it locally only after the node changes.
-    #[test]
-    // It is "ignored", but the node-cli ignored tests are running on the CI.
-    // This can be run locally with `cargo test --release -p node-cli test_sync -- --ignored`.
-    #[ignore]
-    fn test_sync() {
-        let keystore_path = tempfile::tempdir().expect("Creates keystore path");
-        let keystore =
-            sc_keystore::Store::open(keystore_path.path(), None).expect("Creates keystore");
-        let alice = keystore
-            .write()
-            .insert_ephemeral_from_seed::<sc_consensus_babe::AuthorityPair>("//Alice")
-            .expect("Creates authority pair");
-
-        let chain_spec = crate::chain_spec::tests::integration_test_config_with_single_authority();
-
-        // For the block factory
-        let mut slot_num = 1u64;
-
-        // For the extrinsics factory
-        let bob = Arc::new(AccountKeyring::Bob.pair());
-        let charlie = Arc::new(AccountKeyring::Charlie.pair());
-        let mut index = 0;
-
-        sc_service_test::sync(
-            chain_spec,
-            |config| {
-                let mut setup_handles = None;
-                new_full!(
-                    config,
-                    |block_import: &sc_consensus_babe::BabeBlockImport<Block, _, _>,
-                     babe_link: &sc_consensus_babe::BabeLink<Block>| {
-                        setup_handles = Some((block_import.clone(), babe_link.clone()));
-                    }
-                )
-                .map(move |(node, x)| (node, (x, setup_handles.unwrap())))
-            },
-            |config| new_light(config),
-            |service, &mut (ref inherent_data_providers, (ref mut block_import, ref babe_link))| {
-                let mut inherent_data = inherent_data_providers
-                    .create_inherent_data()
-                    .expect("Creates inherent data.");
-                inherent_data.replace_data(sp_finality_tracker::INHERENT_IDENTIFIER, &1u64);
-
-                let parent_id = BlockId::number(service.client().chain_info().best_number);
-                let parent_header = service.client().header(&parent_id).unwrap().unwrap();
-                let parent_hash = parent_header.hash();
-                let parent_number = *parent_header.number();
-
-                futures::executor::block_on(service.transaction_pool().maintain(
-                    ChainEvent::NewBlock {
-                        is_new_best: true,
-                        hash: parent_header.hash(),
-                        tree_route: None,
-                        header: parent_header.clone(),
-                    },
-                ));
-
-                let mut proposer_factory = sc_basic_authorship::ProposerFactory::new(
-                    service.client(),
-                    service.transaction_pool(),
-                    None,
-                );
-
-                let epoch_descriptor = babe_link
-                    .epoch_changes()
-                    .lock()
-                    .epoch_descriptor_for_child_of(
-                        descendent_query(&*service.client()),
-                        &parent_hash,
-                        parent_number,
-                        slot_num,
-                    )
-                    .unwrap()
-                    .unwrap();
-
-                let mut digest = Digest::<H256>::default();
-
-                // even though there's only one authority some slots might be empty,
-                // so we must keep trying the next slots until we can claim one.
-                let babe_pre_digest = loop {
-                    inherent_data.replace_data(
-                        sp_timestamp::INHERENT_IDENTIFIER,
-                        &(slot_num * SLOT_DURATION),
-                    );
-                    if let Some(babe_pre_digest) = sc_consensus_babe::test_helpers::claim_slot(
-                        slot_num,
-                        &parent_header,
-                        &*service.client(),
-                        &keystore,
-                        &babe_link,
-                    ) {
-                        break babe_pre_digest;
-                    }
-
-                    slot_num += 1;
-                };
-
-                digest.push(<DigestItem as CompatibleDigestItem>::babe_pre_digest(
-                    babe_pre_digest,
-                ));
-
-                let new_block = futures::executor::block_on(async move {
-                    let proposer = proposer_factory.init(&parent_header).await;
-                    proposer
-                        .unwrap()
-                        .propose(
-                            inherent_data,
-                            digest,
-                            std::time::Duration::from_secs(1),
-                            RecordProof::Yes,
-                        )
-                        .await
-                })
-                .expect("Error making test block")
-                .block;
-
-                let (new_header, new_body) = new_block.deconstruct();
-                let pre_hash = new_header.hash();
-                // sign the pre-sealed hash of the block and then
-                // add it to a digest item.
-                let to_sign = pre_hash.encode();
-                let signature = alice.sign(&to_sign[..]);
-                let item = <DigestItem as CompatibleDigestItem>::babe_seal(signature.into());
-                slot_num += 1;
-
-                let mut params = BlockImportParams::new(BlockOrigin::File, new_header);
-                params.post_digests.push(item);
-                params.body = Some(new_body);
-                params.intermediates.insert(
-                    Cow::from(INTERMEDIATE_KEY),
-                    Box::new(BabeIntermediate::<Block> { epoch_descriptor }) as Box<dyn Any>,
-                );
-                params.fork_choice = Some(ForkChoiceStrategy::LongestChain);
-
-                block_import
-                    .import_block(params, Default::default())
-                    .expect("error importing test block");
-            },
-            |service, _| {
-                let amount = 5 * CENTS;
-                let to: AccountId = AccountPublic::from(bob.public()).into_account().into();
-                let from: AccountId = AccountPublic::from(charlie.public()).into_account().into();
-                let genesis_hash = service.client().block_hash(0).unwrap().unwrap();
-                let best_block_id = BlockId::number(service.client().chain_info().best_number);
-                let (spec_version, transaction_version) = {
-                    let version = service.client().runtime_version_at(&best_block_id).unwrap();
-                    (version.spec_version, version.transaction_version)
-                };
-                let signer = charlie.clone();
-
-                let function = Call::Balances(BalancesCall::transfer(to.into(), amount));
-
-                let check_spec_version = frame_system::CheckSpecVersion::new();
-                let check_tx_version = frame_system::CheckTxVersion::new();
-                let check_genesis = frame_system::CheckGenesis::new();
-                let check_era = frame_system::CheckEra::from(Era::Immortal);
-                let check_nonce = frame_system::CheckNonce::from(index);
-                let check_weight = frame_system::CheckWeight::new();
-                let payment = pallet_transaction_payment::ChargeTransactionPayment::from(0);
-                let validate_grandpa_equivocation =
-                    pallet_grandpa::ValidateEquivocationReport::new();
-                let extra = (
-                    check_spec_version,
-                    check_tx_version,
-                    check_genesis,
-                    check_era,
-                    check_nonce,
-                    check_weight,
-                    payment,
-                    validate_grandpa_equivocation,
-                );
-                let raw_payload = SignedPayload::from_raw(
-                    function,
-                    extra,
-                    (
-                        spec_version,
-                        transaction_version,
-                        genesis_hash,
-                        genesis_hash,
-                        (),
-                        (),
-                        (),
-                        (),
-                    ),
-                );
-                let signature = raw_payload.using_encoded(|payload| signer.sign(payload));
-                let (function, extra, _) = raw_payload.deconstruct();
-                let xt =
-                    UncheckedExtrinsic::new_signed(function, from.into(), signature.into(), extra)
-                        .encode();
-                let v: Vec<u8> = Decode::decode(&mut xt.as_slice()).unwrap();
-
-                index += 1;
-                OpaqueExtrinsic(v)
-            },
-        );
-    }
-
-    #[test]
-    #[ignore]
-    fn test_consensus() {
-        sc_service_test::consensus(
-            crate::chain_spec::tests::integration_test_config_with_two_authorities(),
-            |config| new_full(config),
-            |config| new_light(config),
-            vec!["//Alice".into(), "//Bob".into()],
-        )
-    }
-}
+// Tests are commented out until we find a solution to why
+// building dependencies for the tests are taking so long on Travis CI
+
+// #[cfg(test)]
+// mod tests {
+//     use crate::node_executor;
+//     use crate::node_rpc;
+//     use crate::service::{new_full, new_light};
+//     use codec::{Decode, Encode};
+//     use node_runtime::RuntimeApi;
+//     use node_runtime::{currency::CENTS, SLOT_DURATION};
+//     use node_runtime::{opaque::Block, AccountId, DigestItem, Signature};
+//     use node_runtime::{BalancesCall, Call, UncheckedExtrinsic};
+//     use sc_consensus_babe::{BabeIntermediate, CompatibleDigestItem, INTERMEDIATE_KEY};
+//     use sc_consensus_epochs::descendent_query;
+//     use sc_finality_grandpa::{self as grandpa};
+//     use sc_service::AbstractService;
+//     use sp_consensus::{
+//         BlockImport, BlockImportParams, BlockOrigin, Environment, ForkChoiceStrategy, Proposer,
+//         RecordProof,
+//     };
+//     use sp_core::{crypto::Pair as CryptoPair, H256};
+//     use sp_finality_tracker;
+//     use sp_keyring::AccountKeyring;
+//     use sp_runtime::traits::IdentifyAccount;
+//     use sp_runtime::{
+//         generic::{BlockId, Digest, Era, SignedPayload},
+//         traits::Verify,
+//         traits::{Block as BlockT, Header as HeaderT},
+//         OpaqueExtrinsic,
+//     };
+//     use sp_timestamp;
+//     use sp_transaction_pool::{ChainEvent, MaintainedTransactionPool};
+//     use std::{any::Any, borrow::Cow, sync::Arc};
+
+//     type AccountPublic = <Signature as Verify>::Signer;
+
+//     // Long running test. Run it locally only after the node changes.
+//     #[test]
+//     // It is "ignored", but the node-cli ignored tests are running on the CI.
+//     // This can be run locally with `cargo test --release -p node-cli test_sync -- --ignored`.
+//     #[ignore]
+//     fn test_sync() {
+//         let keystore_path = tempfile::tempdir().expect("Creates keystore path");
+//         let keystore =
+//             sc_keystore::Store::open(keystore_path.path(), None).expect("Creates keystore");
+//         let alice = keystore
+//             .write()
+//             .insert_ephemeral_from_seed::<sc_consensus_babe::AuthorityPair>("//Alice")
+//             .expect("Creates authority pair");
+
+//         let chain_spec = crate::chain_spec::tests::integration_test_config_with_single_authority();
+
+//         // For the block factory
+//         let mut slot_num = 1u64;
+
+//         // For the extrinsics factory
+//         let bob = Arc::new(AccountKeyring::Bob.pair());
+//         let charlie = Arc::new(AccountKeyring::Charlie.pair());
+//         let mut index = 0;
+
+//         sc_service_test::sync(
+//             chain_spec,
+//             |config| {
+//                 let mut setup_handles = None;
+//                 new_full!(
+//                     config,
+//                     |block_import: &sc_consensus_babe::BabeBlockImport<Block, _, _>,
+//                      babe_link: &sc_consensus_babe::BabeLink<Block>| {
+//                         setup_handles = Some((block_import.clone(), babe_link.clone()));
+//                     }
+//                 )
+//                 .map(move |(node, x)| (node, (x, setup_handles.unwrap())))
+//             },
+//             |config| new_light(config),
+//             |service, &mut (ref inherent_data_providers, (ref mut block_import, ref babe_link))| {
+//                 let mut inherent_data = inherent_data_providers
+//                     .create_inherent_data()
+//                     .expect("Creates inherent data.");
+//                 inherent_data.replace_data(sp_finality_tracker::INHERENT_IDENTIFIER, &1u64);
+
+//                 let parent_id = BlockId::number(service.client().chain_info().best_number);
+//                 let parent_header = service.client().header(&parent_id).unwrap().unwrap();
+//                 let parent_hash = parent_header.hash();
+//                 let parent_number = *parent_header.number();
+
+//                 futures::executor::block_on(service.transaction_pool().maintain(
+//                     ChainEvent::NewBlock {
+//                         is_new_best: true,
+//                         hash: parent_header.hash(),
+//                         tree_route: None,
+//                         header: parent_header.clone(),
+//                     },
+//                 ));
+
+//                 let mut proposer_factory = sc_basic_authorship::ProposerFactory::new(
+//                     service.client(),
+//                     service.transaction_pool(),
+//                     None,
+//                 );
+
+//                 let epoch_descriptor = babe_link
+//                     .epoch_changes()
+//                     .lock()
+//                     .epoch_descriptor_for_child_of(
+//                         descendent_query(&*service.client()),
+//                         &parent_hash,
+//                         parent_number,
+//                         slot_num,
+//                     )
+//                     .unwrap()
+//                     .unwrap();
+
+//                 let mut digest = Digest::<H256>::default();
+
+//                 // even though there's only one authority some slots might be empty,
+//                 // so we must keep trying the next slots until we can claim one.
+//                 let babe_pre_digest = loop {
+//                     inherent_data.replace_data(
+//                         sp_timestamp::INHERENT_IDENTIFIER,
+//                         &(slot_num * SLOT_DURATION),
+//                     );
+//                     if let Some(babe_pre_digest) = sc_consensus_babe::test_helpers::claim_slot(
+//                         slot_num,
+//                         &parent_header,
+//                         &*service.client(),
+//                         &keystore,
+//                         &babe_link,
+//                     ) {
+//                         break babe_pre_digest;
+//                     }
+
+//                     slot_num += 1;
+//                 };
+
+//                 digest.push(<DigestItem as CompatibleDigestItem>::babe_pre_digest(
+//                     babe_pre_digest,
+//                 ));
+
+//                 let new_block = futures::executor::block_on(async move {
+//                     let proposer = proposer_factory.init(&parent_header).await;
+//                     proposer
+//                         .unwrap()
+//                         .propose(
+//                             inherent_data,
+//                             digest,
+//                             std::time::Duration::from_secs(1),
+//                             RecordProof::Yes,
+//                         )
+//                         .await
+//                 })
+//                 .expect("Error making test block")
+//                 .block;
+
+//                 let (new_header, new_body) = new_block.deconstruct();
+//                 let pre_hash = new_header.hash();
+//                 // sign the pre-sealed hash of the block and then
+//                 // add it to a digest item.
+//                 let to_sign = pre_hash.encode();
+//                 let signature = alice.sign(&to_sign[..]);
+//                 let item = <DigestItem as CompatibleDigestItem>::babe_seal(signature.into());
+//                 slot_num += 1;
+
+//                 let mut params = BlockImportParams::new(BlockOrigin::File, new_header);
+//                 params.post_digests.push(item);
+//                 params.body = Some(new_body);
+//                 params.intermediates.insert(
+//                     Cow::from(INTERMEDIATE_KEY),
+//                     Box::new(BabeIntermediate::<Block> { epoch_descriptor }) as Box<dyn Any>,
+//                 );
+//                 params.fork_choice = Some(ForkChoiceStrategy::LongestChain);
+
+//                 block_import
+//                     .import_block(params, Default::default())
+//                     .expect("error importing test block");
+//             },
+//             |service, _| {
+//                 let amount = 5 * CENTS;
+//                 let to: AccountId = AccountPublic::from(bob.public()).into_account().into();
+//                 let from: AccountId = AccountPublic::from(charlie.public()).into_account().into();
+//                 let genesis_hash = service.client().block_hash(0).unwrap().unwrap();
+//                 let best_block_id = BlockId::number(service.client().chain_info().best_number);
+//                 let (spec_version, transaction_version) = {
+//                     let version = service.client().runtime_version_at(&best_block_id).unwrap();
+//                     (version.spec_version, version.transaction_version)
+//                 };
+//                 let signer = charlie.clone();
+
+//                 let function = Call::Balances(BalancesCall::transfer(to.into(), amount));
+
+//                 let check_spec_version = frame_system::CheckSpecVersion::new();
+//                 let check_tx_version = frame_system::CheckTxVersion::new();
+//                 let check_genesis = frame_system::CheckGenesis::new();
+//                 let check_era = frame_system::CheckEra::from(Era::Immortal);
+//                 let check_nonce = frame_system::CheckNonce::from(index);
+//                 let check_weight = frame_system::CheckWeight::new();
+//                 let payment = pallet_transaction_payment::ChargeTransactionPayment::from(0);
+//                 let validate_grandpa_equivocation =
+//                     pallet_grandpa::ValidateEquivocationReport::new();
+//                 let extra = (
+//                     check_spec_version,
+//                     check_tx_version,
+//                     check_genesis,
+//                     check_era,
+//                     check_nonce,
+//                     check_weight,
+//                     payment,
+//                     validate_grandpa_equivocation,
+//                 );
+//                 let raw_payload = SignedPayload::from_raw(
+//                     function,
+//                     extra,
+//                     (
+//                         spec_version,
+//                         transaction_version,
+//                         genesis_hash,
+//                         genesis_hash,
+//                         (),
+//                         (),
+//                         (),
+//                         (),
+//                     ),
+//                 );
+//                 let signature = raw_payload.using_encoded(|payload| signer.sign(payload));
+//                 let (function, extra, _) = raw_payload.deconstruct();
+//                 let xt =
+//                     UncheckedExtrinsic::new_signed(function, from.into(), signature.into(), extra)
+//                         .encode();
+//                 let v: Vec<u8> = Decode::decode(&mut xt.as_slice()).unwrap();
+
+//                 index += 1;
+//                 OpaqueExtrinsic(v)
+//             },
+//         );
+//     }
+
+//     #[test]
+//     #[ignore]
+//     fn test_consensus() {
+//         sc_service_test::consensus(
+//             crate::chain_spec::tests::integration_test_config_with_two_authorities(),
+//             |config| new_full(config),
+//             |config| new_light(config),
+//             vec!["//Alice".into(), "//Bob".into()],
+//         )
+//     }
+// }

+ 3 - 0
package.json

@@ -23,6 +23,9 @@
     "pioneer/packages/apps*",
     "pioneer/packages/page*",
     "pioneer/packages/react*",
+    "pioneer/packages/joy-utils",
+    "pioneer/packages/joy-members",
+    "pioneer/packages/joy-pages",
     "utils/api-examples"
   ],
   "resolutions": {

+ 0 - 3
pioneer/.eslintignore

@@ -2,16 +2,13 @@
 **/coverage/*
 **/node_modules/*
 packages/old-apps/*
-packages/joy-members/*
 packages/joy-election/*
 packages/joy-forum/*
 packages/joy-help/*
 packages/joy-media/*
-packages/joy-pages/*
 packages/joy-proposals/*
 packages/joy-roles/*
 packages/joy-settings/*
-packages/joy-utils/*
 packages/joy-utils-old/*
 .eslintrc.js
 i18next-scanner.config.js

+ 8 - 2
pioneer/.eslintrc.js

@@ -13,14 +13,20 @@ module.exports = {
   rules: {
     ...base.rules,
     '@typescript-eslint/no-explicit-any': 'off',
-    'react/prop-types': 'off',
     'new-cap': 'off',
     '@typescript-eslint/interface-name-prefix': 'off',
     '@typescript-eslint/ban-ts-comment': 'error',
     // why only required in VSCode!?!? is eslint plugin not working like eslint commandline?
     // Or are we having to add this because of new versions of eslint-config-* ?
     'no-console': 'off',
-    'header/header': 'off' // Temporary disable polkadot's rule
+    // Override some extended config rules:
+    'camelcase': 'off',
+    'header/header': 'off',
+    'sort-keys': 'off',
+    'react/jsx-sort-props': 'off',
+    'react/jsx-max-props-per-line': 'off',
+    'sort-destructure-keys/sort-destructure-keys': 'off',
+    '@typescript-eslint/unbound-method': 'warn', // Doesn't work well with our version of Formik, see: https://github.com/formium/formik/issues/2589
   },
   // isolate pioneer from monorepo eslint rules
   root: true

+ 3 - 1
pioneer/packages/apps-config/src/api/spec/index.ts

@@ -10,6 +10,7 @@ import encointerNodeTeeproxy from './encointer-node-teeproxy';
 import kulupu from './kulupu';
 import nodeTemplate from './node-template';
 import stablePoc from './stable-poc';
+import joystreamNode from './joystream-node';
 
 export default {
   acala,
@@ -21,5 +22,6 @@ export default {
   kulupu,
   'node-template': nodeTemplate,
   'stable-poc': stablePoc,
-  stable_poc: stablePoc
+  stable_poc: stablePoc,
+  'joystream-node': joystreamNode
 };

+ 3 - 0
pioneer/packages/apps-config/src/api/spec/joystream-node.ts

@@ -0,0 +1,3 @@
+import { types } from '@joystream/types';
+
+export default types;

+ 5 - 0
pioneer/packages/apps-config/src/settings/endpoints.ts

@@ -29,6 +29,11 @@ function createDev (t: TFunction): LinkOption[] {
 
 function createLive (t: TFunction): LinkOption[] {
   return [
+    {
+      info: 'joystream',
+      text: t<string>('rpc.joystream', 'Joystream (Current Testnet, hosted by Jsgenesis)', { ns: 'apps-config' }),
+      value: 'wss://rome-rpc-endpoint.joystream.org:9944'
+    },
     {
       dnslink: 'polkadot',
       info: 'polkadot',

+ 3 - 1
pioneer/packages/apps-config/src/ui/logos/index.ts

@@ -17,6 +17,7 @@ import nodeNodle from './nodes/nodle.svg';
 import nodePolkadot from './nodes/polkadot-circle.svg';
 import nodePolkadotJs from './nodes/polkadot-js.svg';
 import nodeSubstrate from './nodes/substrate-hexagon.svg';
+import nodeJoystream from './nodes/joystream-node.svg';
 
 // extensions
 import extensionPolkadotJs from './extensions/polkadot-js.svg';
@@ -48,7 +49,8 @@ const nodeLogos: Record<string, any> = [
   ['Nodle Chain Node', nodeNodle],
   ['parity-polkadot', nodePolkadot],
   ['polkadot-js', nodePolkadotJs],
-  ['substrate-node', nodeSubstrate]
+  ['substrate-node', nodeSubstrate],
+  ['joystream-node', nodeJoystream]
 ].reduce((logos, [node, logo]): Record<string, any> => ({
   ...logos,
   [(node as string).toLowerCase().replace(/-/g, ' ')]: logo

+ 1 - 0
pioneer/packages/apps-config/src/ui/logos/nodes/joystream-node.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240"><defs><style>.cls-1{fill:#4038ff;}.cls-2{fill:#fff;}</style></defs><title>Icon-mono-white-1bg-blue</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_14" data-name="Layer 14"><rect class="cls-1" width="240" height="240"/><path class="cls-2" d="M135.28,49.73l12.7,0-.15,59.67a57,57,0,0,1-14.49,37.86,67.76,67.76,0,0,0,1.72-15Z"/><path class="cls-2" d="M94.28,153.78v0a34.19,34.19,0,0,1-26.15,12.61L72,153.73Z"/><path class="cls-2" d="M102,130.94v1.28a34,34,0,0,1-2,11.41l-25-.06,3.83-12.69Z"/><path class="cls-2" d="M158.14,49.78l12.7,0-.09,36.84a57,57,0,0,1-14.49,37.86,67.76,67.76,0,0,0,1.72-15Z"/><path class="cls-2" d="M125.11,49.69l-.21,82.59a57.22,57.22,0,0,1-57.32,57H61.23l3.83-12.69h2.56a44.5,44.5,0,0,0,44.57-44.35l.22-82.58Z"/></g></g></svg>

+ 21 - 41
pioneer/packages/apps-routing/src/index.ts

@@ -9,69 +9,49 @@ import appSettings from '@polkadot/ui-settings';
 // When adding here, also ensure to add to Dummy.tsx
 
 import accounts from './accounts';
-import claims from './claims';
-import contracts from './contracts';
-import council from './council';
-// import dashboard from './dashboard';
-import democracy from './democracy';
 import explorer from './explorer';
 import extrinsics from './extrinsics';
-import genericAsset from './generic-asset';
 import js from './js';
-import parachains from './parachains';
-import poll from './poll';
 import settings from './settings';
-import society from './society';
 import staking from './staking';
 import storage from './storage';
 import sudo from './sudo';
-import techcomm from './techcomm';
 import toolbox from './toolbox';
 import transfer from './transfer';
-import treasury from './treasury';
+// Joy packages
+import members from './joy-members';
+import { terms, privacyPolicy } from './joy-pages';
 
 export default function create (t: <T = string> (key: string, text: string, options: { ns: string }) => T): Routes {
   return appSettings.uiMode === 'light'
     ? [
-      // dashboard,
-      explorer(t),
-      accounts(t),
-      claims(t),
-      poll(t),
-      transfer(t),
-      genericAsset(t),
-      null,
+      members(t),
       staking(t),
-      democracy(t),
-      council(t),
-      // TODO Not sure about the inclusion of treasury, parachains & society here
       null,
-      settings(t)
+      transfer(t),
+      accounts(t),
+      settings(t),
+      // Those are hidden
+      terms(t),
+      privacyPolicy(t)
     ]
     : [
-      // dashboard(t),
-      explorer(t),
-      accounts(t),
-      claims(t),
-      poll(t),
-      transfer(t),
-      genericAsset(t),
-      null,
+      members(t),
       staking(t),
-      democracy(t),
-      council(t),
-      treasury(t),
-      techcomm(t),
-      parachains(t),
-      society(t),
       null,
-      contracts(t),
+      transfer(t),
+      accounts(t),
+      settings(t),
+      null,
+      explorer(t),
       storage(t),
       extrinsics(t),
+      js(t),
+      toolbox(t),
       sudo(t),
       null,
-      settings(t),
-      toolbox(t),
-      js(t)
+      // Those are hidden
+      terms(t),
+      privacyPolicy(t)
     ];
 }

+ 15 - 0
pioneer/packages/apps-routing/src/joy-members.ts

@@ -0,0 +1,15 @@
+import { Route } from './types';
+
+import Members from '@polkadot/joy-members/index';
+
+export default function create (t: <T = string> (key: string, text: string, options: { ns: string }) => T): Route {
+  return {
+    Component: Members,
+    display: {
+      needsApi: ['query.members.nextMemberId']
+    },
+    icon: 'users',
+    name: 'members',
+    text: t<string>('nav.membership', 'Membership', { ns: 'apps-routing' })
+  };
+}

+ 27 - 0
pioneer/packages/apps-routing/src/joy-pages.ts

@@ -0,0 +1,27 @@
+import { Route } from './types';
+
+import { ToS, Privacy } from '@polkadot/joy-pages/index';
+
+export function terms (t: <T = string> (key: string, text: string, options: { ns: string }) => T): Route {
+  return {
+    Component: ToS,
+    display: {
+      isHidden: true
+    },
+    text: t<string>('nav.terms', 'Terms of Service', { ns: 'apps-routing' }),
+    icon: 'file',
+    name: 'pages/tos'
+  };
+}
+
+export function privacyPolicy (t: <T = string> (key: string, text: string, options: { ns: string }) => T): Route {
+  return {
+    Component: Privacy,
+    display: {
+      isHidden: true
+    },
+    text: t<string>('nav.privacy', 'Privacy Policy', { ns: 'apps-routing' }),
+    icon: 'file',
+    name: 'pages/privacy'
+  };
+}

BIN
pioneer/packages/apps/public/favicon.ico


+ 11 - 0
pioneer/packages/apps/public/index.html

@@ -6,6 +6,17 @@
     <link rel="manifest" href="manifest.json">
     <link rel="shortcut icon" href="favicon.ico">
     <title><%= htmlWebpackPlugin.options.PAGE_TITLE %></title>
+    <% if (htmlWebpackPlugin.options.IS_PROD) { %>
+      <!-- Global site tag (gtag.js) - Google Analytics -->
+      <script async src="https://www.googletagmanager.com/gtag/js?id=UA-133429788-6"></script>
+      <script>
+        window.dataLayer = window.dataLayer || [];
+        function gtag(){dataLayer.push(arguments);}
+        gtag('js', new Date());
+
+        gtag('config', 'UA-133429788-6', { 'anonymize_ip': true });
+      </script>
+    <% } %>
     <script type="text/javascript" src="/env-config.js"></script>
   </head>
   <body>

+ 14 - 1
pioneer/packages/apps/src/Apps.tsx

@@ -21,6 +21,9 @@ import SideBar from './SideBar';
 import WarmUp from './WarmUp';
 import { WindowDimensionsCtx } from './WindowDimensions';
 
+/* Joystream-specific */
+import TopBar from './JoyTopBar/TopBar';
+
 interface SidebarState {
   isCollapsed: boolean;
   isMenu: boolean;
@@ -96,7 +99,10 @@ function Apps ({ className = '' }: Props): React.ReactElement<Props> {
             toggleMenu={_toggleMenu}
           />
           <Signer>
-            <Content />
+            <div className='apps--Main'>
+              <TopBar />
+              <Content />
+            </div>
           </Signer>
           <ConnectingOverlay />
           <div id={PORTAL_ID} />
@@ -222,4 +228,11 @@ export default React.memo(styled(Apps)`
       opacity: 1;
     }
   }
+
+  .apps--Main {
+    flex-grow: 1;
+    min-height: 100vh;
+    overflow-x: hidden;
+    overflow-y: auto;
+  }
 `);

+ 1 - 1
pioneer/packages/apps/src/Content/NotFound.tsx

@@ -7,7 +7,7 @@ import { Redirect } from 'react-router';
 
 function NotFound (): React.ReactElement {
   return (
-    <Redirect to='/explorer' />
+    <Redirect to='/staking' />
   );
 }
 

+ 2 - 6
pioneer/packages/apps/src/Content/index.tsx

@@ -78,15 +78,11 @@ function Content ({ className }: Props): React.ReactElement<Props> {
 }
 
 export default React.memo(styled(Content)`
-  background: #f5f4f3;
-  flex-grow: 1;
-  height: 100%;
-  min-height: 100vh;
-  overflow-x: hidden;
-  overflow-y: auto;
+  background: rgba(250, 250, 250);
   padding: 0 1.5rem;
   position: relative;
   width: 100%;
+  height: 100%;
 
   @media(max-width: 768px) {
     padding: 0 0.5rem;

+ 56 - 0
pioneer/packages/apps/src/JoyTopBar/TopBar.tsx

@@ -0,0 +1,56 @@
+import React from 'react';
+import { useMyMembership } from '@polkadot/joy-utils/react/hooks';
+import { InputAddress } from '@polkadot/react-components';
+import { Available } from '@polkadot/react-query';
+import styled from 'styled-components';
+import { useApi } from '@polkadot/react-hooks';
+
+const StyledTopBar = styled.div`
+  padding: 0.75rem;
+  background-color: #3f3f3f;
+  border-bottom: 1px solid #d4d4d5;
+  text-align: right;
+  margin: 0;
+
+  &.NoMyAddress {
+    background-color: #ffeb83;
+    color: #000;
+    text-align: center;
+  }
+
+  .ui--InputAddress {
+    display: inline-block;
+  }
+`;
+
+function JoyTopBar () {
+  const {
+    allAccounts,
+    myAddress
+  } = useMyMembership();
+
+  const { isApiReady } = useApi();
+
+  if (!isApiReady) {
+    return null;
+  }
+
+  const balance = <span className='label'>Balance: </span>;
+  const labelExtra = myAddress
+    ? <Available label={balance} params={myAddress} />
+    : 'No key selected';
+
+  return Object.keys(allAccounts || {}).length ? (
+    <StyledTopBar>
+      <InputAddress
+        defaultValue={myAddress}
+        help='My current key that signs transactions'
+        label='My key'
+        labelExtra={labelExtra}
+        type='account'
+      />
+    </StyledTopBar>
+  ) : null;
+}
+
+export default JoyTopBar;

+ 1 - 1
pioneer/packages/apps/src/SideBar/ChainInfo.tsx

@@ -24,7 +24,7 @@ function ChainInfo ({ className = '', onClick }: Props): React.ReactElement<Prop
 
   return (
     <div
-      className={`apps--SideBar-logo ${className} ui--highlight--border`}
+      className={`apps--SideBar-logo ${className}`}
       onClick={onClick}
     >
       <div className='apps--SideBar-logo-inner'>

+ 3 - 24
pioneer/packages/apps/src/SideBar/index.tsx

@@ -7,7 +7,7 @@ import { Routes } from '@polkadot/apps-routing/types';
 import React, { useCallback, useMemo, useState } from 'react';
 import styled from 'styled-components';
 import createRoutes from '@polkadot/apps-routing';
-import { Button, ChainImg, Icon, Menu, media } from '@polkadot/react-components';
+import { Button, ChainImg, Menu, media } from '@polkadot/react-components';
 
 import { SIDEBAR_MENU_THRESHOLD } from '../constants';
 import NetworkModal from '../modals/Network';
@@ -101,27 +101,6 @@ function SideBar ({ className = '', collapse, handleResize, isCollapsed, isMenuO
                 )
             ))}
             <Menu.Divider hidden />
-            <Menu.Item className='apps--SideBar-Item'>
-              <a
-                className='apps--SideBar-Item-NavLink'
-                href='https://github.com/polkadot-js/apps'
-                rel='noopener noreferrer'
-                target='_blank'
-              >
-                <Icon icon='code-branch' /><span className='text'>{t<string>('nav.github', 'GitHub', { ns: 'apps-routing' })}</span>
-              </a>
-            </Menu.Item>
-            <Menu.Item className='apps--SideBar-Item'>
-              <a
-                className='apps--SideBar-Item-NavLink'
-                href='https://wiki.polkadot.network'
-                rel='noopener noreferrer'
-                target='_blank'
-              >
-                <Icon icon='book' /><span className='text'>{t<string>('nav.wiki', 'Wiki', { ns: 'apps-routing' })}</span>
-              </a>
-            </Menu.Item>
-            <Menu.Divider hidden />
             {!isCollapsed && <NodeInfo />}
           </div>
           <div className={`apps--SideBar-collapse ${isCollapsed ? 'collapsed' : 'expanded'}`}>
@@ -159,7 +138,7 @@ export default React.memo(styled(SideBar)`
 
   .apps--SideBar {
     align-items: center;
-    background: #4f5255;
+    background: #3f3f3f;
     box-sizing: border-box;
     display: flex;
     flex-flow: column;
@@ -221,7 +200,7 @@ export default React.memo(styled(SideBar)`
     }
 
     .apps--SideBar-collapse {
-      background: #4f5255;
+      background: #3f3f3f;
       bottom: 0;
       left: 0;
       padding: 0.75rem 0 .75rem 0.65rem;

+ 20 - 13
pioneer/packages/apps/src/index.tsx

@@ -20,6 +20,9 @@ import settings from '@polkadot/ui-settings';
 import Apps from './Apps';
 import WindowDimensions from './WindowDimensions';
 
+/* Joystream-specific */
+import { MyMembershipProvider, MyAccountProvider } from '@polkadot/joy-utils/react/context';
+
 const rootId = 'root';
 const rootElement = document.getElementById(rootId);
 const theme = { theme: settings.uiTheme };
@@ -38,19 +41,23 @@ store.each((_, key): void => {
 ReactDOM.render(
   <Suspense fallback='...'>
     <ThemeProvider theme={theme}>
-      <Queue>
-        <Api url={settings.apiUrl}>
-          <BlockAuthors>
-            <Events>
-              <HashRouter>
-                <WindowDimensions>
-                  <Apps />
-                </WindowDimensions>
-              </HashRouter>
-            </Events>
-          </BlockAuthors>
-        </Api>
-      </Queue>
+      <MyAccountProvider>
+        <Queue>
+          <Api url={settings.apiUrl}>
+            <MyMembershipProvider>
+              <BlockAuthors>
+                <Events>
+                  <HashRouter>
+                    <WindowDimensions>
+                      <Apps />
+                    </WindowDimensions>
+                  </HashRouter>
+                </Events>
+              </BlockAuthors>
+            </MyMembershipProvider>
+          </Api>
+        </Queue>
+      </MyAccountProvider>
     </ThemeProvider>
   </Suspense>,
   rootElement

+ 3 - 11
pioneer/packages/apps/webpack.base.config.js

@@ -98,7 +98,7 @@ function createWebpack (ENV, context) {
           ]
         },
         {
-          exclude: [/semantic-ui-css/],
+          // Original config had "exclude: [/semantic-ui-css/]"
           test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
           use: [
             {
@@ -112,7 +112,8 @@ function createWebpack (ENV, context) {
           ]
         },
         {
-          exclude: [/semantic-ui-css/],
+          // Original config had "exclude: [/semantic-ui-css/]", because Semantic UI Icons
+          // are not used in polkadot-js/apps repository, but they are used in ours
           test: [/\.eot$/, /\.ttf$/, /\.svg$/, /\.woff$/, /\.woff2$/],
           use: [
             {
@@ -123,15 +124,6 @@ function createWebpack (ENV, context) {
               }
             }
           ]
-        },
-        {
-          include: [/semantic-ui-css/],
-          test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/, /\.eot$/, /\.ttf$/, /\.svg$/, /\.woff$/, /\.woff2$/],
-          use: [
-            {
-              loader: require.resolve('null-loader')
-            }
-          ]
         }
       ]
     },

+ 2 - 1
pioneer/packages/apps/webpack.config.js

@@ -20,7 +20,8 @@ module.exports = merge(
     devtool: process.env.BUILD_ANALYZE ? 'source-map' : false,
     plugins: [
       new HtmlWebpackPlugin({
-        PAGE_TITLE: 'Polkadot/Substrate Portal',
+        IS_PROD: ENV === 'production',
+        PAGE_TITLE: 'Joystream Network Portal',
         inject: true,
         template: path.join(context, `${hasPublic ? 'public/' : ''}index.html`)
       })

+ 0 - 0
pioneer/packages/joy-members/.skip-build


+ 3 - 3
pioneer/packages/joy-members/package.json

@@ -7,9 +7,9 @@
   "author": "Joystream contributors",
   "maintainers": [],
   "dependencies": {
-    "@babel/runtime": "^7.7.1",
-    "@polkadot/react-components": "0.37.0-beta.63",
-    "@polkadot/react-query": "0.37.0-beta.63",
+    "@babel/runtime": "^7.10.5",
+    "@polkadot/react-components": "0.51.1",
+    "@polkadot/react-query": "0.51.1",
     "@polkadot/joy-utils": "^0.1.1"
   }
 }

+ 47 - 29
pioneer/packages/joy-members/src/Dashboard.tsx

@@ -3,12 +3,12 @@ import BN from 'bn.js';
 
 import { ApiProps } from '@polkadot/react-api/types';
 import { I18nProps } from '@polkadot/react-components/types';
-import { withCalls } from '@polkadot/react-api/with';
-import { Bubble } from '@polkadot/react-components/index';
+import { withCalls } from '@polkadot/react-api/hoc';
+import { Label } from 'semantic-ui-react';
 import { formatNumber } from '@polkadot/util';
 import { bool as Bool } from '@polkadot/types';
 
-import Section from '@polkadot/joy-utils/Section';
+import { Section } from '@polkadot/joy-utils/react/components';
 import translate from './translate';
 import { queryMembershipToProp } from './utils';
 
@@ -27,39 +27,57 @@ class Dashboard extends React.PureComponent<Props> {
   renderGeneral () {
     const p = this.props;
     const { newMembershipsAllowed: isAllowed } = p;
-    let isAllowedColor = '';
+    let isAllowedColor: 'grey' | 'green' | 'red' = 'grey';
+
     if (isAllowed) {
       isAllowedColor = isAllowed.eq(true) ? 'green' : 'red';
     }
-    return <Section title='General'>
-      <Bubble label='New memberships allowed?' className={isAllowedColor}>
-        {isAllowed && (isAllowed.eq(true) ? 'Yes' : 'No')}
-      </Bubble>
-      <Bubble label='Next member ID'>
-        {formatNumber(p.nextMemberId)}
-      </Bubble>
-      <Bubble label='First member ID'>
-        {formatNumber(FIRST_MEMBER_ID)}
-      </Bubble>
-    </Section>;
+
+    return (
+      <Section title='General'>
+        <Label.Group size='large'>
+          <Label color={isAllowedColor}>
+            New memberships allowed?
+            <Label.Detail>{isAllowed && (isAllowed.eq(true) ? 'Yes' : 'No')}</Label.Detail>
+          </Label>
+          <Label color='grey'>
+            Next member ID
+            <Label.Detail>{formatNumber(p.nextMemberId)}</Label.Detail>
+          </Label>
+          <Label color='grey'>
+            First member ID
+            <Label.Detail>{formatNumber(FIRST_MEMBER_ID)}</Label.Detail>
+          </Label>
+        </Label.Group>
+      </Section>
+    );
   }
 
   renderValidation () {
     const p = this.props;
-    return <Section title='Validation'>
-      <Bubble label='Min. length of handle'>
-        {formatNumber(p.minHandleLength)} chars
-      </Bubble>
-      <Bubble label='Max. length of handle'>
-        {formatNumber(p.maxHandleLength)} chars
-      </Bubble>
-      <Bubble label='Max. length of avatar URI'>
-        {formatNumber(p.maxAvatarUriLength)} chars
-      </Bubble>
-      <Bubble label='Max. length of about'>
-        {formatNumber(p.maxAboutTextLength)} chars
-      </Bubble>
-    </Section>;
+
+    return (
+      <Section title='Validation'>
+        <Label.Group color='grey' size='large'>
+          <Label>
+            Min. length of handle
+            <Label.Detail>{formatNumber(p.minHandleLength)} chars</Label.Detail>
+          </Label>
+          <Label>
+            Max. length of handle
+            <Label.Detail>{formatNumber(p.maxHandleLength)} chars</Label.Detail>
+          </Label>
+          <Label>
+            Max. length of avatar URI
+            <Label.Detail>{formatNumber(p.maxAvatarUriLength)} chars</Label.Detail>
+          </Label>
+          <Label>
+            Max. length of about
+            <Label.Detail>{formatNumber(p.maxAboutTextLength)} chars</Label.Detail>
+          </Label>
+        </Label.Group>
+      </Section>
+    );
   }
 
   render () {

+ 27 - 23
pioneer/packages/joy-members/src/Details.tsx

@@ -5,18 +5,18 @@ import ReactMarkdown from 'react-markdown';
 import { IdentityIcon } from '@polkadot/react-components';
 import { ApiProps } from '@polkadot/react-api/types';
 import { I18nProps } from '@polkadot/react-components/types';
-import { withCalls } from '@polkadot/react-api/with';
+import { withCalls } from '@polkadot/react-api/hoc';
 import { Option } from '@polkadot/types';
 import BalanceDisplay from '@polkadot/react-components/Balance';
-import AddressMini from '@polkadot/react-components/AddressMiniJoy';
+import AddressMini from '@polkadot/react-components/AddressMini';
 import { formatNumber } from '@polkadot/util';
 
 import translate from './translate';
 import { MemberId, Membership, EntryMethod, Paid, Screening, Genesis, SubscriptionId } from '@joystream/types/members';
 import { queryMembershipToProp } from './utils';
 import { Seat } from '@joystream/types/council';
-import { nonEmptyStr, queryToProp } from '@polkadot/joy-utils/index';
-import { MyAccountProps, withMyAccount } from '@polkadot/joy-utils/MyAccount';
+import { nonEmptyStr, queryToProp } from '@polkadot/joy-utils/functions/misc';
+import { MyAccountProps, withMyAccount } from '@polkadot/joy-utils/react/hocs/accounts';
 
 type Props = ApiProps & I18nProps & MyAccountProps & {
   preview?: boolean;
@@ -28,6 +28,7 @@ type Props = ApiProps & I18nProps & MyAccountProps & {
 class Component extends React.PureComponent<Props> {
   render () {
     const { membership } = this.props;
+
     return membership && !membership.handle.isEmpty
       ? this.renderProfile(membership)
       : (
@@ -54,34 +55,34 @@ class Component extends React.PureComponent<Props> {
     const hasAvatar = avatar_uri && nonEmptyStr(avatar_uri.toString());
     const isMyProfile = myAddress && (myAddress === root_account.toString() || myAddress === controller_account.toString());
     const isCouncilor: boolean = (
-      (activeCouncil.find(x => root_account.eq(x.member)) !== undefined) ||
-      (activeCouncil.find(x => controller_account.eq(x.member)) !== undefined)
+      (activeCouncil.find((x) => root_account.eq(x.member)) !== undefined) ||
+      (activeCouncil.find((x) => controller_account.eq(x.member)) !== undefined)
     );
 
     return (
       <>
-      <div className={`item ProfileDetails ${isMyProfile && 'MyProfile'}`}>
-        {hasAvatar
-          ? <img className='ui avatar image' src={avatar_uri.toString()} />
-          : <IdentityIcon className='image' value={root_account} size={40} />
-        }
-        <div className='content'>
-          <div className='header'>
-            <Link to={`/members/${handle.toString()}`} className='handle'>{handle.toString()}</Link>
-            {isMyProfile && <Link to={'/members/edit'} className='ui tiny button'>Edit my profile</Link>}
-          </div>
-          <div className='description'>
-            {isCouncilor &&
-              <b className='muted text' style={{ color: '#607d8b' }}>
+        <div className={`item ProfileDetails${isMyProfile ? ' MyProfile' : ''}`}>
+          {hasAvatar
+            ? <img className='ui avatar image' src={avatar_uri.toString()} />
+            : <IdentityIcon className='image' value={root_account} size={40} />
+          }
+          <div className='content'>
+            <div className='header'>
+              <Link to={`/members/${handle.toString()}`} className='handle'>{handle.toString()}</Link>
+              {isMyProfile && <Link to={'/members/edit'} className='ui tiny button'>Edit my profile</Link>}
+            </div>
+            <div className='description'>
+              {isCouncilor &&
+              <b className='muted text' style={{ color: '#607d8b', display: 'block' }}>
                 <i className='university icon'></i>
                 Council member
               </b>}
-            <BalanceDisplay label='Balance(root): ' params={root_account} />
-            <div>MemberId: {this.props.memberId.toString()}</div>
+              <BalanceDisplay label='Balance(root): ' params={root_account} />
+              <div>MemberId: {this.props.memberId.toString()}</div>
+            </div>
           </div>
         </div>
-      </div>
-      {!preview && this.renderDetails(membership, isCouncilor)}
+        {!preview && this.renderDetails(membership, isCouncilor)}
       </>
     );
   }
@@ -147,11 +148,14 @@ class Component extends React.PureComponent<Props> {
 
   private renderEntryMethod (entry: EntryMethod) {
     const etype = entry.type;
+
     if (etype === Paid.name) {
       const paid = entry.value as Paid;
+
       return <div>Paid, terms ID: {paid.toNumber()}</div>;
     } else if (etype === Screening.name) {
       const accountId = entry.value as Screening;
+
       return <div>Screened by <AddressMini value={accountId} isShort={false} isPadded={false} withBalance /></div>;
     } else if (etype === Genesis.name) {
       return <div>Created at Genesis</div>;

+ 3 - 1
pioneer/packages/joy-members/src/DetailsByHandle.tsx

@@ -1,7 +1,7 @@
 import React from 'react';
 
 import { I18nProps } from '@polkadot/react-components/types';
-import { withCalls } from '@polkadot/react-api/with';
+import { withCalls } from '@polkadot/react-api/hoc';
 import { stringToU8a, u8aToHex } from '@polkadot/util';
 
 import translate from './translate';
@@ -16,6 +16,7 @@ type DetailsByHandleProps = {
 
 function DetailsByHandleInner (p: DetailsByHandleProps) {
   const { memberIdByHandle: memberId } = p;
+
   return memberId !== undefined // here we can't make distinction value existing and loading
     ? <div className='ui massive relaxed middle aligned list FullProfile'>
       <Details memberId={memberId} />
@@ -39,6 +40,7 @@ class Component extends React.PureComponent<Props> {
   render () {
     const { match: { params: { handle } } } = this.props;
     const handleHex = u8aToHex(stringToU8a(handle));
+
     return (
       <DetailsByHandle handle={handleHex} />
     );

+ 53 - 45
pioneer/packages/joy-members/src/EditForm.tsx

@@ -1,19 +1,18 @@
 import BN from 'bn.js';
-import React from 'react';
+import React, { useContext } from 'react';
 import { Link } from 'react-router-dom';
 import { Form, Field, withFormik, FormikProps } from 'formik';
 import * as Yup from 'yup';
 
 import { Vec } from '@polkadot/types';
-import Section from '@polkadot/joy-utils/Section';
-import TxButton from '@polkadot/joy-utils/TxButton';
-import * as JoyForms from '@polkadot/joy-utils/forms';
+import { Section, TxButton } from '@polkadot/joy-utils/react/components';
+import * as JoyForms from '@polkadot/joy-utils/react/components/forms';
 import { SubmittableResult } from '@polkadot/api';
 import { MemberId, Membership, PaidTermId, PaidMembershipTerms } from '@joystream/types/members';
 import { OptionText } from '@joystream/types/common';
-import { MyAccountProps, withMyAccount } from '@polkadot/joy-utils/MyAccount';
+import { MyAccountProps, withMyAccount } from '@polkadot/joy-utils/react/hocs/accounts';
 import { queryMembershipToProp } from './utils';
-import { withCalls } from '@polkadot/react-api/index';
+import { withCalls, ApiContext } from '@polkadot/react-api/index';
 import { Button, Message } from 'semantic-ui-react';
 import { formatBalance } from '@polkadot/util';
 import { TxFailedCallback, TxCallback } from '@polkadot/react-components/Status/types';
@@ -79,12 +78,15 @@ const InnerForm = (props: FormProps) => {
     memberId
   } = props;
 
+  const { api } = useContext(ApiContext);
+
   const onSubmit = (sendTx: () => void) => {
     if (isValid) sendTx();
   };
 
   const onTxFailed: TxFailedCallback = (txResult: SubmittableResult | null) => {
     setSubmitting(false);
+
     if (txResult == null) {
       // Tx cancelled.
 
@@ -102,7 +104,9 @@ const InnerForm = (props: FormProps) => {
 
   // TODO extract to forms.tsx
   const fieldToTextOption = (field: FieldName): OptionText => {
-    return isFieldChanged(field) ? OptionText.some(values[field]) : OptionText.none();
+    return isFieldChanged(field)
+      ? api.createType('Option<Text>', values[field])
+      : api.createType('Option<Text>', null);
   };
 
   const buildTxParams = () => {
@@ -126,29 +130,29 @@ const InnerForm = (props: FormProps) => {
   // TODO show warning that you don't have enough balance to buy a membership
 
   return (
-    <Section title="My Membership Profile">
-      <Form className="ui form JoyForm">
+    <Section title='My Membership Profile'>
+      <Form className='ui form JoyForm'>
         <LabelledText
-          name="handle"
-          label="Handle/nickname"
+          name='handle'
+          label='Handle/nickname'
           placeholder={'You can use a-z, 0-9 and underscores.'}
           style={{ maxWidth: '30rem' }}
           {...props}
         />
         <LabelledText
-          name="avatar"
-          label="Avatar URL"
-          placeholder="Paste here an URL of your avatar image."
+          name='avatar'
+          label='Avatar URL'
+          placeholder='Paste here an URL of your avatar image.'
           {...props}
         />
-        <LabelledField name="about" label="About" {...props}>
+        <LabelledField name='about' label='About' {...props}>
           <Field
-            component="textarea"
-            id="about"
-            name="about"
+            component='textarea'
+            id='about'
+            name='about'
             disabled={isSubmitting}
             rows={3}
-            placeholder="Write here anything you would like to share about yourself with Joystream community."
+            placeholder='Write here anything you would like to share about yourself with Joystream community.'
           />
         </LabelledField>
         {!profile && paidTerms && (
@@ -165,24 +169,26 @@ const InnerForm = (props: FormProps) => {
           </Message>
         )}
         <LabelledField invisibleLabel {...props}>
-          <TxButton
-            type="submit"
-            size="large"
-            label={profile ? 'Update my profile' : 'Register'}
-            isDisabled={!dirty || isSubmitting}
-            params={buildTxParams()}
-            tx={profile ? 'members.updateMembership' : 'members.buyMembership'}
-            onClick={onSubmit}
-            txFailedCb={onTxFailed}
-            txSuccessCb={onTxSuccess}
-          />
-          <Button
-            type="button"
-            size="large"
-            disabled={!dirty || isSubmitting}
-            onClick={() => resetForm()}
-            content="Reset form"
-          />
+          <div style={{ display: 'flex' }}>
+            <TxButton
+              type='submit'
+              size='large'
+              label={profile ? 'Update my profile' : 'Register'}
+              isDisabled={!dirty || isSubmitting}
+              params={buildTxParams()}
+              tx={profile ? 'members.updateMembership' : 'members.buyMembership'}
+              onClick={onSubmit}
+              txFailedCb={onTxFailed}
+              txSuccessCb={onTxSuccess}
+            />
+            <Button
+              type='button'
+              size='large'
+              disabled={!dirty || isSubmitting}
+              onClick={() => resetForm()}
+              content='Reset form'
+            />
+          </div>
         </LabelledField>
       </Form>
     </Section>
@@ -191,8 +197,9 @@ const InnerForm = (props: FormProps) => {
 
 const EditForm = withFormik<OuterProps, FormValues>({
   // Transform outer props into form values
-  mapPropsToValues: props => {
+  mapPropsToValues: (props) => {
     const { profile: p } = props;
+
     return {
       handle: p ? p.handle.toString() : '',
       avatar: p ? p.avatar_uri.toString() : '',
@@ -202,7 +209,7 @@ const EditForm = withFormik<OuterProps, FormValues>({
 
   validationSchema: buildSchema,
 
-  handleSubmit: values => {
+  handleSubmit: (values) => {
     // do submitting things
   }
 })(InnerForm);
@@ -220,6 +227,7 @@ type WithMembershipDataProps = {
 
 function WithMembershipDataInner (p: WithMembershipDataProps) {
   const triedToFindProfile = !p.memberId || p.membership;
+
   if (
     triedToFindProfile &&
     p.paidTerms &&
@@ -228,7 +236,9 @@ function WithMembershipDataInner (p: WithMembershipDataProps) {
     p.maxAvatarUriLength &&
     p.maxAboutTextLength
   ) {
-    const membership = p.membership && !p.membership.handle.isEmpty ? p.membership : undefined;
+    const membership = (p.memberId && p.membership && !p.membership.handle.isEmpty)
+      ? p.membership
+      : undefined;
 
     if (!membership && p.paidTerms.isEmpty) {
       console.error('Could not find active paid membership terms');
@@ -267,10 +277,10 @@ type WithMembershipDataWrapperProps = MyAccountProps & {
 function WithMembershipDataWrapperInner (p: WithMembershipDataWrapperProps) {
   if (p.allAccounts && !Object.keys(p.allAccounts).length) {
     return (
-      <Message warning className="JoyMainStatus">
+      <Message warning className='JoyMainStatus'>
         <Message.Header>Please create a key to get started.</Message.Header>
         <div style={{ marginTop: '1rem' }}>
-          <Link to={'/accounts'} className="ui button orange">
+          <Link to={'/accounts'} className='ui button orange'>
             Create key
           </Link>
         </div>
@@ -280,9 +290,7 @@ function WithMembershipDataWrapperInner (p: WithMembershipDataWrapperProps) {
 
   if (p.memberIdsByRootAccountId && p.memberIdsByControllerAccountId && p.paidTermsIds) {
     if (p.paidTermsIds.length) {
-      // let member_ids = p.memberIdsByRootAccountId.slice(); // u8a.subarray is not a function!!
-      p.memberIdsByRootAccountId.concat(p.memberIdsByControllerAccountId);
-      const memberId = p.memberIdsByRootAccountId.length ? p.memberIdsByRootAccountId[0] : undefined;
+      const [memberId] = p.memberIdsByRootAccountId.toArray().concat(p.memberIdsByControllerAccountId.toArray());
 
       return <WithMembershipData memberId={memberId} paidTermsId={p.paidTermsIds[0]} />;
     } else {

+ 11 - 6
pioneer/packages/joy-members/src/List.tsx

@@ -4,13 +4,14 @@ import React from 'react';
 import { ApiProps } from '@polkadot/react-api/types';
 import { I18nProps } from '@polkadot/react-components/types';
 
-import Section from '@polkadot/joy-utils/Section';
+import { Section } from '@polkadot/joy-utils/react/components';
 import translate from './translate';
 import Details from './Details';
 import { MemberId } from '@joystream/types/members';
 import { RouteComponentProps, Redirect } from 'react-router-dom';
 import { Pagination, Icon, PaginationProps } from 'semantic-ui-react';
 import styled from 'styled-components';
+import { withApi } from '@polkadot/react-api';
 
 const StyledPagination = styled(Pagination)`
   border-bottom: 1px solid #ddd !important;
@@ -22,7 +23,7 @@ type Props = ApiProps & I18nProps & RouteComponentProps & {
   match: { params: { page?: string } };
 };
 
-type State = {};
+type State = Record<any, never>;
 
 const MEMBERS_PER_PAGE = 20;
 
@@ -31,7 +32,8 @@ class Component extends React.PureComponent<Props, State> {
 
   onPageChange = (e: React.MouseEvent, data: PaginationProps) => {
     const { history } = this.props;
-    history.push(`/members/list/${data.activePage}`);
+
+    history.push(`/members/list/${data.activePage || 1}`);
   }
 
   renderPagination (currentPage: number, pagesCount: number) {
@@ -55,7 +57,8 @@ class Component extends React.PureComponent<Props, State> {
     const {
       firstMemberId,
       membersCreated,
-      match: { params: { page } }
+      match: { params: { page } },
+      api
     } = this.props;
 
     const membersCount = membersCreated.toNumber();
@@ -67,11 +70,13 @@ class Component extends React.PureComponent<Props, State> {
     }
 
     const ids: MemberId[] = [];
+
     if (membersCount > 0) {
       const firstId = firstMemberId.toNumber() + (currentPage - 1) * MEMBERS_PER_PAGE;
       const lastId = Math.min(firstId + MEMBERS_PER_PAGE, membersCount) - 1;
+
       for (let i = firstId; i <= lastId; i++) {
-        ids.push(new MemberId(i));
+        ids.push(api.createType('MemberId', i));
       }
     }
 
@@ -95,4 +100,4 @@ class Component extends React.PureComponent<Props, State> {
   }
 }
 
-export default translate(Component);
+export default translate(withApi(Component));

+ 8 - 8
pioneer/packages/joy-members/src/MemberPreview.tsx

@@ -3,7 +3,7 @@ import { Link } from 'react-router-dom';
 
 import { ApiProps } from '@polkadot/react-api/types';
 import { I18nProps } from '@polkadot/react-components/types';
-import { withCalls, withMulti } from '@polkadot/react-api/with';
+import { withCalls, withMulti } from '@polkadot/react-api/hoc';
 import { Vec } from '@polkadot/types';
 import { AccountId } from '@polkadot/types/interfaces';
 import IdentityIcon from '@polkadot/react-components/IdentityIcon';
@@ -12,9 +12,8 @@ import translate from './translate';
 import { MemberId, Membership } from '@joystream/types/members';
 import { queryMembershipToProp } from './utils';
 import { Seat } from '@joystream/types/council';
-import { nonEmptyStr, queryToProp } from '@polkadot/joy-utils/index';
-import { FlexCenter } from '@polkadot/joy-utils/FlexCenter';
-import { MutedSpan } from '@polkadot/joy-utils/MutedText';
+import { nonEmptyStr, queryToProp } from '@polkadot/joy-utils/functions/misc';
+import { FlexCenter, MutedSpan } from '@polkadot/joy-utils/react/components';
 
 const AvatarSizePx = 36;
 const InlineAvatarSizePx = 24;
@@ -33,6 +32,7 @@ type MemberPreviewProps = ApiProps & I18nProps & {
 class InnerMemberPreview extends React.PureComponent<MemberPreviewProps> {
   render () {
     const { membership } = this.props;
+
     return membership && !membership.handle.isEmpty
       ? this.renderProfile(membership)
       : null;
@@ -43,19 +43,19 @@ class InnerMemberPreview extends React.PureComponent<MemberPreviewProps> {
     const { handle, avatar_uri } = membership;
 
     const hasAvatar = avatar_uri && nonEmptyStr(avatar_uri.toString());
-    const isCouncilor: boolean = accountId !== undefined && activeCouncil.find(x => accountId.eq(x.member)) !== undefined;
+    const isCouncilor: boolean = accountId !== undefined && activeCouncil.find((x) => accountId.eq(x.member)) !== undefined;
 
     const avatarSize = inline ? InlineAvatarSizePx : AvatarSizePx;
 
-    return <div className={`JoyMemberPreview ${className}`} style={style}>
+    return <div className={`JoyMemberPreview ${className || ''}`} style={style}>
       <FlexCenter>
         {prefixLabel &&
           <MutedSpan className='PrefixLabel'>{prefixLabel}</MutedSpan>
         }
         {hasAvatar ? (
-          <img className="Avatar" src={avatar_uri.toString()} width={avatarSize} height={avatarSize} />
+          <img className='Avatar' src={avatar_uri.toString()} width={avatarSize} height={avatarSize} />
         ) : (
-          <IdentityIcon className="Avatar" value={accountId} size={avatarSize} />
+          <IdentityIcon className='Avatar' value={accountId} size={avatarSize} />
         )
         }
         <div className='Content'>

+ 0 - 58
pioneer/packages/joy-members/src/index.css

@@ -1,58 +0,0 @@
-.ProfilePreviews,
-.FullProfile {
-  .item {
-    .image {
-      padding: 0 !important;
-    }
-    .description {
-      font-size: 1rem;
-    }
-  }
-}
-.ProfilePreviews {
-  &.ui.list>.item:first-child {
-    padding-top: .75rem;
-  }
-  &.ui.list>.item:last-child {
-    padding-bottom: .75rem;
-  }
-  .MyProfile {
-    background-color: #FFF8E1;
-  }
-}
-.ProfileDetails {
-  padding-left: 1rem !important;
-  .handle {
-    margin-right: 1rem;
-    .button {
-      padding: .5rem .75rem;
-    }
-  }
-}
-.ProfileDetailsTable {
-  font-size: 1rem !important;
-  tr td:first-child {
-    width: 1%;
-    white-space: nowrap;
-  }
-}
-
-.JoyMemberPreview {
-  margin-right: .5rem;
-  .PrefixLabel {
-    margin-right: .5rem;
-  }
-  .Avatar {
-    margin-right: .5rem;
-    border-radius: 100%;
-  }
-  .Content {
-    .Username {
-      font-weight: bold;
-    }
-    .Details {
-      font-weight: 100;
-      opacity: .75;
-    }
-  }
-}

+ 16 - 11
pioneer/packages/joy-members/src/index.tsx

@@ -5,10 +5,9 @@ import { Route, Switch } from 'react-router';
 
 import { AppProps, I18nProps } from '@polkadot/react-components/types';
 import { ApiProps } from '@polkadot/react-api/types';
-import { withCalls, withMulti } from '@polkadot/react-api/with';
-import Tabs, { TabItem } from '@polkadot/react-components/Tabs';
-
-import './index.css';
+import { withCalls, withMulti } from '@polkadot/react-api/hoc';
+import Tabs from '@polkadot/react-components/Tabs';
+import { TabItem } from '@polkadot/react-components/Tabs/types';
 
 import { queryMembershipToProp } from './utils';
 import translate from './translate';
@@ -16,10 +15,15 @@ import Dashboard from './Dashboard';
 import List from './List';
 import DetailsByHandle from './DetailsByHandle';
 import EditForm from './EditForm';
-import { withMyAccount, MyAccountProps } from '@polkadot/joy-utils/MyAccount';
+import { withMyAccount, MyAccountProps } from '@polkadot/joy-utils/react/hocs/accounts';
 import { FIRST_MEMBER_ID } from './constants';
 import { RouteComponentProps } from 'react-router-dom';
 
+import styled from 'styled-components';
+import style from './style';
+
+const MembersMain = styled.main`${style}`;
+
 // define out internal types
 type Props = AppProps & ApiProps & I18nProps & MyAccountProps & {
   nextMemberId?: BN;
@@ -32,8 +36,8 @@ class App extends React.PureComponent<Props> {
     return [
       {
         name: 'list',
-        text: t('All members') + ` (${memberCount})`,
-        forcedExact: false
+        text: t('All members') + ` (${memberCount?.toString() || '-'})`,
+        forceMatchParams: true
       },
       {
         name: 'edit',
@@ -48,6 +52,7 @@ class App extends React.PureComponent<Props> {
 
   private renderList (routeProps: RouteComponentProps) {
     const { nextMemberId, ...otherProps } = this.props;
+
     return nextMemberId
       ? <List firstMemberId={FIRST_MEMBER_ID} membersCreated={nextMemberId} {...otherProps} {...routeProps}/>
       : <em>Loading...</em>;
@@ -58,18 +63,18 @@ class App extends React.PureComponent<Props> {
     const tabs = this.buildTabs();
 
     return (
-      <main className='members--App'>
+      <MembersMain className='members--App'>
         <header>
           <Tabs basePath={basePath} items={tabs} />
         </header>
         <Switch>
           <Route path={`${basePath}/edit`} component={EditForm} />
           <Route path={`${basePath}/dashboard`} component={Dashboard} />
-          <Route path={`${basePath}/list/:page([0-9]+)?`} render={ props => this.renderList(props) } />
+          <Route path={`${basePath}/list/:page([0-9]+)?`} render={ (props) => this.renderList(props) } />
           <Route exact={true} path={`${basePath}/:handle`} component={DetailsByHandle} />
-          <Route render={ props => this.renderList(props) } />
+          <Route render={ (props) => this.renderList(props) } />
         </Switch>
-      </main>
+      </MembersMain>
     );
   }
 }

+ 62 - 0
pioneer/packages/joy-members/src/style.ts

@@ -0,0 +1,62 @@
+import { css } from 'styled-components';
+
+export default css`
+  .ProfilePreviews,
+  .FullProfile {
+    .item {
+      .image {
+        padding: 0 !important;
+      }
+      .description {
+        font-size: 1rem;
+      }
+    }
+  }
+  .ProfilePreviews {
+    &.ui.list>.item:first-child {
+      padding-top: .75rem;
+    }
+    &.ui.list>.item:last-child {
+      padding-bottom: .75rem;
+    }
+    .MyProfile {
+      background-color: #FFF8E1;
+    }
+  }
+  .ProfileDetails {
+    padding-left: 1rem !important;
+    .handle {
+      margin-right: 1rem;
+      .button {
+        padding: .5rem .75rem;
+      }
+    }
+  }
+  .ProfileDetailsTable {
+    font-size: 1rem !important;
+    tr td:first-child {
+      width: 1%;
+      white-space: nowrap;
+    }
+  }
+
+  .JoyMemberPreview {
+    margin-right: .5rem;
+    .PrefixLabel {
+      margin-right: .5rem;
+    }
+    .Avatar {
+      margin-right: .5rem;
+      border-radius: 100%;
+    }
+    .Content {
+      .Username {
+        font-weight: bold;
+      }
+      .Details {
+        font-weight: 100;
+        opacity: .75;
+      }
+    }
+  }
+`;

+ 3 - 2
pioneer/packages/joy-members/src/utils.ts

@@ -1,5 +1,6 @@
-import { queryToProp } from '@polkadot/joy-utils/index';
-import { Options as QueryOptions } from '@polkadot/react-api/with/types';
+// TODO: Move to joy-utils?
+import { queryToProp } from '@polkadot/joy-utils/functions/misc';
+import { Options as QueryOptions } from '@polkadot/react-api/hoc/types';
 
 export const queryMembershipToProp = (storageItem: string, paramNameOrOpts?: string | QueryOptions) => {
   return queryToProp(`query.members.${storageItem}`, paramNameOrOpts);

+ 0 - 0
pioneer/packages/joy-pages/.skip-build


+ 3 - 3
pioneer/packages/joy-pages/package.json

@@ -7,9 +7,9 @@
   "author": "Joystream contributors",
   "maintainers": [],
   "dependencies": {
-    "@babel/runtime": "^7.7.1",
-    "@polkadot/react-components": "0.37.0-beta.63",
-    "@polkadot/react-query": "0.37.0-beta.63",
+    "@babel/runtime": "^7.10.5",
+    "@polkadot/react-components": "0.51.1",
+    "@polkadot/react-query": "0.51.1",
     "@polkadot/joy-utils": "^0.1.1"
   }
 }

+ 4 - 2
pioneer/packages/joy-pages/src/index.tsx

@@ -4,9 +4,11 @@ import Page from './Page';
 import ToS_md from './md/ToS.md';
 
 import Privacy_md from './md/Privacy.md';
+
 export function ToS () {
-  return <Page md={ToS_md} />;
+  return <Page md={ToS_md as string} />;
 }
+
 export function Privacy () {
-  return <Page md={Privacy_md} />;
+  return <Page md={Privacy_md as string} />;
 }

+ 0 - 14
pioneer/packages/joy-utils-old/src/functions/misc.ts

@@ -1,14 +0,0 @@
-import { Bytes } from '@polkadot/types/primitive';
-
-export function includeKeys<T extends { [k: string]: any }> (obj: T, ...allowedKeys: string[]) {
-  return Object.keys(obj).filter(objKey => {
-    return allowedKeys.reduce(
-      (hasAllowed: boolean, allowedKey: string) => hasAllowed || objKey.includes(allowedKey),
-      false
-    );
-  });
-}
-
-export function bytesToString (bytes: Bytes) {
-  return Buffer.from(bytes.toString().substr(2), 'hex').toString();
-}

+ 0 - 1
pioneer/packages/joy-utils-old/src/react/components/index.tsx

@@ -1 +0,0 @@
-export { default as PromiseComponent } from './PromiseComponent';

+ 0 - 1
pioneer/packages/joy-utils-old/src/react/context/index.tsx

@@ -1 +0,0 @@
-export { TransportContext, TransportProvider } from './transport';

+ 0 - 0
pioneer/packages/joy-utils-old/README.md → pioneer/packages/joy-utils/README.md


+ 3 - 3
pioneer/packages/joy-utils-old/package.json → pioneer/packages/joy-utils/package.json

@@ -7,9 +7,9 @@
   "author": "Joystream contributors",
   "maintainers": [],
   "dependencies": {
-    "@babel/runtime": "^7.7.1",
-    "@polkadot/react-components": "0.37.0-beta.63",
-    "@polkadot/react-query": "0.37.0-beta.63",
+    "@babel/runtime": "^7.10.5",
+    "@polkadot/react-components": "0.51.1",
+    "@polkadot/react-query": "0.51.1",
     "@types/query-string": "^6.2.0",
     "@types/uuid": "^3.4.4",
     "@types/yup": "^0.26.10",

+ 0 - 0
pioneer/packages/joy-utils-old/src/functions/date.ts → pioneer/packages/joy-utils/src/functions/date.ts


+ 2 - 2
pioneer/packages/joy-utils-old/src/functions/format.ts → pioneer/packages/joy-utils/src/functions/format.ts

@@ -16,7 +16,7 @@ export const formatReward = (
     : next_payment_at_block;
 
   return (
-    `${formatBalance(amount)}${interval.isSome ? ` / ${interval.unwrap()} block(s)` : ''}` +
-    ((showNextPaymentBlock && nextPaymentBlock) ? ` (Next payment: #${nextPaymentBlock})` : '')
+    `${formatBalance(amount)}${interval.isSome ? ` / ${interval.unwrap().toString()} block(s)` : ''}` +
+    ((showNextPaymentBlock && nextPaymentBlock) ? ` (Next payment: #${nextPaymentBlock.toString()})` : '')
   );
 };

+ 167 - 0
pioneer/packages/joy-utils/src/functions/misc.ts

@@ -0,0 +1,167 @@
+import { Bytes } from '@polkadot/types/primitive';
+import BN from 'bn.js';
+import keyring from '@polkadot/ui-keyring';
+import { ElectionStake, Backer } from '@joystream/types/council';
+import { Options as QueryOptions } from '@polkadot/react-api/hoc/types';
+import queryString from 'query-string';
+import { SubmittableResult } from '@polkadot/api';
+import { Codec } from '@polkadot/types/types';
+
+export const ZERO = new BN(0);
+
+export function bnToStr (bn?: BN, dflt = ''): string {
+  return bn ? bn.toString() : dflt;
+}
+
+export const notDefined = (x: any): boolean =>
+  x === null || typeof x === 'undefined';
+
+export const isDefined = (x: any): boolean =>
+  !notDefined(x);
+
+export const isDef = isDefined;
+
+export const notDef = notDefined;
+
+export const isObj = (x: any): boolean =>
+  x !== null && typeof x === 'object';
+
+export const isStr = (x: any): boolean =>
+  typeof x === 'string';
+
+export const isNum = (x: any): boolean =>
+  typeof x === 'number';
+
+export const isEmptyStr = (x: any): boolean =>
+  notDefined(x) || (isStr(x) && (x as string).trim().length === 0);
+
+export const nonEmptyStr = (x?: any) =>
+  isStr(x) && (x as string).trim().length > 0;
+
+export const parseNumStr = (num: string): number | undefined => {
+  try {
+    return parseInt(num, undefined);
+  } catch (err) {
+    return undefined;
+  }
+};
+
+export const nonEmptyArr = (x: any): boolean =>
+  Array.isArray(x) && x.length > 0;
+
+export const isEmptyArr = (x: any): boolean =>
+  !nonEmptyArr(x);
+
+export function findNameByAddress (address: string): string | undefined {
+  let keyring_address;
+
+  try {
+    keyring_address = keyring.getAccount(address);
+  } catch (error) {
+    try {
+      keyring_address = keyring.getAddress(address);
+    } catch (error) {
+      // do nothing
+    }
+  }
+
+  return keyring_address ? keyring_address.meta.name : undefined;
+}
+
+export function isKnownAddress (address: string): boolean {
+  return isDefined(findNameByAddress(address));
+}
+
+export function calcTotalStake (stakes: ElectionStake | ElectionStake[] | undefined): BN {
+  if (typeof stakes === 'undefined') {
+    return ZERO;
+  }
+
+  const total = (stake: ElectionStake) => stake.new.add(stake.transferred);
+
+  try {
+    if (Array.isArray(stakes)) {
+      return stakes.reduce((accum, stake) => {
+        return accum.add(total(stake));
+      }, ZERO);
+    } else {
+      return total(stakes);
+    }
+  } catch (err) {
+    console.log('Failed to calculate a total stake', stakes, err);
+
+    return ZERO;
+  }
+}
+
+export function calcBackersStake (backers: Backer[]): BN {
+  return backers.map((b) => b.stake).reduce((accum, stake) => {
+    return accum.add(stake);
+  }, ZERO);
+}
+
+/** Example of apiQuery: 'query.councilElection.round' */
+export function queryToProp (
+  apiQuery: string,
+  paramNameOrOpts?: string | QueryOptions
+): [string, QueryOptions] {
+  let paramName: string | undefined;
+  let propName: string | undefined;
+
+  if (typeof paramNameOrOpts === 'string') {
+    paramName = paramNameOrOpts;
+  } else if (paramNameOrOpts) {
+    paramName = paramNameOrOpts.paramName;
+    propName = paramNameOrOpts.propName;
+  }
+
+  // If prop name is still undefined, derive it from the name of storage item:
+  if (!propName) {
+    propName = apiQuery.split('.').slice(-1)[0];
+  }
+
+  return [apiQuery, { paramName, propName }];
+}
+
+export function getUrlParam (location: Location, paramName: string, deflt: string | null = null): string | null {
+  const params = queryString.parse(location.search);
+
+  return params[paramName] ? params[paramName] as string : deflt;
+}
+
+export function filterSubstrateEventsAndExtractData (txResult: SubmittableResult, eventName: string): Codec[][] {
+  const res: Codec[][] = [];
+
+  txResult.events.forEach((event) => {
+    const { event: { method, data } } = event;
+
+    if (method === eventName) {
+      res.push(data.toArray());
+    }
+  });
+
+  return res;
+}
+
+export function findFirstParamOfSubstrateEvent<T extends Codec> (txResult: SubmittableResult, eventName: string): T | undefined {
+  const data = filterSubstrateEventsAndExtractData(txResult, eventName);
+
+  if (data && data.length) {
+    return data[0][0] as T;
+  }
+
+  return undefined;
+}
+
+export function includeKeys<T extends { [k: string]: any }> (obj: T, ...allowedKeys: string[]) {
+  return Object.keys(obj).filter((objKey) => {
+    return allowedKeys.reduce(
+      (hasAllowed: boolean, allowedKey: string) => hasAllowed || objKey.includes(allowedKey),
+      false
+    );
+  });
+}
+
+export function bytesToString (bytes: Bytes) {
+  return Buffer.from(bytes.toString().substr(2), 'hex').toString();
+}

+ 1 - 1
pioneer/packages/joy-utils-old/src/FlexCenter.tsx → pioneer/packages/joy-utils/src/react/components/FlexCenter.tsx

@@ -1,5 +1,5 @@
 import React from 'react';
 
-export function FlexCenter (props: React.PropsWithChildren<{}>) {
+export function FlexCenter (props: React.PropsWithChildren<unknown>) {
   return <div className='FlexCenter'>{props.children}</div>;
 }

+ 4 - 1
pioneer/packages/joy-utils-old/src/MutedText.tsx → pioneer/packages/joy-utils/src/react/components/MutedText.tsx

@@ -8,15 +8,18 @@ type Props = React.PropsWithChildren<{
 
 function getClassNames (props: Props): string {
   const { smaller = false, className } = props;
-  return `grey text ${smaller ? 'smaller' : ''} ${className}`;
+
+  return `grey text ${smaller ? 'smaller' : ''} ${className || ''}`;
 }
 
 export const MutedSpan = (props: Props) => {
   const { style, children } = props;
+
   return <span className={getClassNames(props)} style={style}>{children}</span>;
 };
 
 export const MutedDiv = (props: Props) => {
   const { style, children } = props;
+
   return <div className={getClassNames(props)} style={style}>{children}</div>;
 };

+ 3 - 0
pioneer/packages/joy-utils-old/src/Section.tsx → pioneer/packages/joy-utils/src/react/components/Section.tsx

@@ -46,6 +46,7 @@ type Props = BareProps & {
 export default class Section extends React.PureComponent<Props> {
   render () {
     let { className, children, pagination } = this.props;
+
     className = (className || '') + ' JoySection';
 
     return (
@@ -62,10 +63,12 @@ export default class Section extends React.PureComponent<Props> {
 
   private renderTitle = () => {
     const { title, level = 2, pagination } = this.props;
+
     if (!title) return null;
 
     const className = 'JoySection-title';
     const style = pagination ? { margin: '0' } : {};
+
     return React.createElement(
       `h${level}`,
       { className, style },

+ 46 - 50
pioneer/packages/joy-utils-old/src/TxButton.tsx → pioneer/packages/joy-utils/src/react/components/TxButton.tsx

@@ -5,11 +5,8 @@ import { Button } from '@polkadot/react-components/index';
 import { QueueConsumer } from '@polkadot/react-components/Status/Context';
 import { withApi } from '@polkadot/react-api/index';
 import { assert } from '@polkadot/util';
-import { withMyAccount, MyAccountProps } from '@polkadot/joy-utils/MyAccount';
-import { useTransportContext } from '@polkadot/joy-media/TransportContext';
-import { MockTransport } from '@polkadot/joy-media/transport.mock';
-import { Button$Sizes } from '@polkadot/react-components/Button/types';
-import { SemanticShorthandItem, IconProps } from 'semantic-ui-react';
+import { withMyAccount, MyAccountProps } from '../hocs/accounts';
+import { IconName } from '@fortawesome/fontawesome-svg-core';
 
 type InjectedProps = {
   queueExtrinsic: QueueTxExtrinsicAdd;
@@ -20,9 +17,7 @@ export type OnTxButtonClick = (sendTx: () => void) => void;
 type BasicButtonProps = {
   accountId?: string;
   type?: 'submit' | 'button';
-  size?: Button$Sizes;
   isBasic?: boolean;
-  isPrimary?: boolean;
   isDisabled?: boolean;
   label?: React.ReactNode;
   params: Array<any>;
@@ -32,7 +27,7 @@ type BasicButtonProps = {
   style?: Record<string, string | number>;
   children?: React.ReactNode;
   compact?: boolean;
-  icon?: boolean | SemanticShorthandItem<IconProps>;
+  icon?: IconName;
 
   onClick?: OnTxButtonClick;
   txFailedCb?: TxFailedCallback;
@@ -45,15 +40,14 @@ type PropsWithApi = BareProps & ApiProps & MyAccountProps & PartialQueueTxExtrin
 
 class TxButtonInner extends React.PureComponent<PropsWithApi & InjectedProps> {
   render () {
-    const { myAddress, accountId, isPrimary = true, isDisabled, icon = '', onClick } = this.props;
+    const { myAddress, accountId, isDisabled, icon = 'check', onClick } = this.props;
     const origin = accountId || myAddress;
 
     return (
       <Button
         {...this.props}
         isDisabled={isDisabled || !origin}
-        isPrimary={isPrimary}
-        icon={icon as string}
+        icon={icon}
         onClick={() => {
           if (onClick) onClick(this.send);
           else this.send();
@@ -74,7 +68,7 @@ class TxButtonInner extends React.PureComponent<PropsWithApi & InjectedProps> {
 
     queueExtrinsic({
       accountId: origin,
-      extrinsic: api.tx[section][method](...params) as any, // ???
+      extrinsic: api.tx[section][method](...params),
       txFailedCb,
       txSuccessCb,
       txStartCb,
@@ -98,41 +92,43 @@ class TxButton extends React.PureComponent<PropsWithApi> {
   }
 }
 
-const SubstrateTxButton = withApi(withMyAccount(TxButton));
-
-const mockSendTx = () => {
-  const msg = 'Cannot send a Substrate tx in a mock mode';
-  if (typeof window !== 'undefined') {
-    window.alert(`WARN: ${msg}`);
-  } else if (typeof console.warn === 'function') {
-    console.warn(msg);
-  } else {
-    console.log(`WARN: ${msg}`);
-  }
-};
-
-function MockTxButton (props: BasicButtonProps) {
-  const { isPrimary = true, icon = '', onClick } = props;
-
-  return (
-    <Button
-      {...props}
-      isPrimary={isPrimary}
-      icon={icon as string}
-      onClick={() => {
-        if (onClick) onClick(mockSendTx);
-        else mockSendTx();
-      }}
-    />
-  );
-}
-
-function ResolvedButton (props: BasicButtonProps) {
-  const isMock = useTransportContext() instanceof MockTransport;
-
-  return isMock
-    ? <MockTxButton {...props} />
-    : <SubstrateTxButton {...props} />;
-}
-
-export default ResolvedButton;
+export default withApi(withMyAccount(TxButton));
+
+// const SubstrateTxButton = withApi(withMyAccount(TxButton));
+
+// const mockSendTx = () => {
+//   const msg = 'Cannot send a Substrate tx in a mock mode';
+//   if (typeof window !== 'undefined') {
+//     window.alert(`WARN: ${msg}`);
+//   } else if (typeof console.warn === 'function') {
+//     console.warn(msg);
+//   } else {
+//     console.log(`WARN: ${msg}`);
+//   }
+// };
+
+// function MockTxButton (props: BasicButtonProps) {
+//   const { isPrimary = true, icon = '', onClick } = props;
+
+//   return (
+//     <Button
+//       {...props}
+//       isPrimary={isPrimary}
+//       icon={icon as string}
+//       onClick={() => {
+//         if (onClick) onClick(mockSendTx);
+//         else mockSendTx();
+//       }}
+//     />
+//   );
+// }
+
+// function ResolvedButton (props: BasicButtonProps) {
+//   const isMock = useTransportContext() instanceof MockTransport;
+
+//   return isMock
+//     ? <MockTxButton {...props} />
+//     : <SubstrateTxButton {...props} />;
+// }
+
+// export default ResolvedButton;

+ 5 - 5
pioneer/packages/joy-utils-old/src/forms.tsx → pioneer/packages/joy-utils/src/react/components/forms.tsx

@@ -1,7 +1,6 @@
 import React from 'react';
 import { Field, FormikErrors, FormikTouched } from 'formik';
-
-import { nonEmptyStr } from '@polkadot/joy-utils/index';
+import { nonEmptyStr } from '../../functions/misc';
 import { Popup, Icon } from 'semantic-ui-react';
 
 export type LabelledProps<FormValues> = {
@@ -38,9 +37,9 @@ export function LabelledField<FormValues> () {
       const renderLabel = () =>
         nonEmptyStr(label)
           ? <>
-              {required && <b style={{ color: 'red' }} title='This field is required'>* </b>}
-              {label}
-            </>
+            {required && <b style={{ color: 'red' }} title='This field is required'>* </b>}
+            {label}
+          </>
           : null;
 
       return (label || invisibleLabel)
@@ -57,6 +56,7 @@ export function LabelledField<FormValues> () {
           {fieldWithError}
         </div>;
     };
+
   return LabelledFieldInner;
 }
 

+ 4 - 0
pioneer/packages/joy-utils/src/react/components/index.tsx

@@ -0,0 +1,4 @@
+export { default as Section } from './Section';
+export { default as TxButton } from './TxButton';
+export { MutedSpan, MutedDiv } from './MutedText';
+export { FlexCenter } from './FlexCenter';

+ 30 - 10
pioneer/packages/joy-utils-old/src/MyAccountContext.tsx → pioneer/packages/joy-utils/src/react/context/account.tsx

@@ -1,11 +1,14 @@
-import React, { useReducer, createContext, useContext, useEffect } from 'react';
+import React, { useReducer, createContext, useEffect } from 'react';
 import store from 'store';
 
-export const MY_ADDRESS = 'joy.myAddress';
+export const ACCOUNT_CHANGED_EVENT_NAME = 'account-changed';
+export const MY_ADDRESS_STORAGE_KEY = 'joy.myAddress';
 
 function readMyAddress (): string | undefined {
-  const myAddress: string | undefined = store.get(MY_ADDRESS);
+  const myAddress = store.get(MY_ADDRESS_STORAGE_KEY) as string | undefined;
+
   console.log('Read my address from the local storage:', myAddress);
+
   return myAddress;
 }
 
@@ -22,7 +25,8 @@ type MyAccountAction = {
 function reducer (state: MyAccountState, action: MyAccountAction): MyAccountState {
   function forget () {
     console.log('Forget my address');
-    store.remove(MY_ADDRESS);
+    store.remove(MY_ADDRESS_STORAGE_KEY);
+
     return { ...state, address: undefined };
   }
 
@@ -32,29 +36,35 @@ function reducer (state: MyAccountState, action: MyAccountAction): MyAccountStat
     case 'reload': {
       address = readMyAddress();
       console.log('Reload my address:', address);
+
       return { ...state, address, inited: true };
     }
 
     case 'set': {
       address = action.address;
+
       if (address !== state.address) {
         if (address) {
           console.log('Set my new address:', address);
-          store.set(MY_ADDRESS, address);
+          store.set(MY_ADDRESS_STORAGE_KEY, address);
+
           return { ...state, address, inited: true };
         } else {
           return forget();
         }
       }
+
       return state;
     }
 
     case 'forget': {
       address = action.address;
       const isMyAddress = address && address === readMyAddress();
+
       if (!address || isMyAddress) {
         return forget();
       }
+
       return state;
     }
 
@@ -88,9 +98,23 @@ const contextStub: MyAccountContextProps = {
 
 export const MyAccountContext = createContext<MyAccountContextProps>(contextStub);
 
-export function MyAccountProvider (props: React.PropsWithChildren<{}>) {
+export function MyAccountProvider (props: React.PropsWithChildren<unknown>) {
   const [state, dispatch] = useReducer(reducer, initialState);
 
+  const handleAccountChangeEvent = (e: Event) => {
+    const { detail: address } = e as CustomEvent<string>;
+
+    dispatch({ type: 'set', address });
+  };
+
+  useEffect(() => {
+    window.addEventListener(ACCOUNT_CHANGED_EVENT_NAME, handleAccountChangeEvent);
+
+    return () => {
+      window.removeEventListener(ACCOUNT_CHANGED_EVENT_NAME, handleAccountChangeEvent);
+    };
+  });
+
   useEffect(() => {
     if (!state.inited) {
       dispatch({ type: 'reload' });
@@ -110,7 +134,3 @@ export function MyAccountProvider (props: React.PropsWithChildren<{}>) {
     </MyAccountContext.Provider>
   );
 }
-
-export function useMyAccount () {
-  return useContext(MyAccountContext);
-}

+ 2 - 0
pioneer/packages/joy-utils/src/react/context/index.tsx

@@ -0,0 +1,2 @@
+export { MyAccountContext, MyAccountProvider } from './account';
+export { MyMembershipContext, MyMembershipProvider } from './membership';

+ 2 - 6
pioneer/packages/joy-utils-old/src/MyMembershipContext.tsx → pioneer/packages/joy-utils/src/react/context/membership.tsx

@@ -1,5 +1,5 @@
-import React, { createContext, useContext } from 'react';
-import { MyAccountProps, withMyAccount } from './MyAccount';
+import React, { createContext } from 'react';
+import { MyAccountProps, withMyAccount } from '../hocs/accounts';
 
 export const MyMembershipContext = createContext<MyAccountProps>({});
 
@@ -12,7 +12,3 @@ function InnerMyMembershipProvider (props: React.PropsWithChildren<MyAccountProp
 }
 
 export const MyMembershipProvider = withMyAccount(InnerMyMembershipProvider);
-
-export function useMyMembership () {
-  return useContext(MyMembershipContext);
-}

+ 0 - 0
pioneer/packages/joy-utils-old/src/react/helpers/index.ts → pioneer/packages/joy-utils/src/react/helpers/index.ts


+ 124 - 0
pioneer/packages/joy-utils/src/react/hocs/accounts.tsx

@@ -0,0 +1,124 @@
+import React, { useContext } from 'react';
+
+import { AccountId } from '@polkadot/types/interfaces';
+import { Vec, Option } from '@polkadot/types';
+import accountObservable from '@polkadot/ui-keyring/observable/accounts';
+import { withCalls, withMulti, withObservable, ApiContext } from '@polkadot/react-api/index';
+import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
+
+import { MemberId, Membership } from '@joystream/types/members';
+import { LeadId } from '@joystream/types/content-working-group';
+
+import { queryMembershipToProp } from '@polkadot/joy-members/utils';
+import useMyAccount from '../hooks/useMyAccount';
+import { componentName } from '../helpers';
+
+export type MyAddressProps = {
+  myAddress?: string;
+};
+
+export type MyAccountProps = MyAddressProps & {
+  myAccountId?: AccountId;
+  myMemberId?: MemberId;
+  memberIdsByRootAccountId?: Vec<MemberId>;
+  memberIdsByControllerAccountId?: Vec<MemberId>;
+  myMemberIdChecked?: boolean;
+  iAmMember?: boolean;
+  myMembership?: Membership | null;
+
+  // Content Working Group
+  curatorEntries?: any; // entire linked_map: CuratorId => Curator
+  isLeadSet?: Option<LeadId>;
+  contentLeadId?: LeadId;
+  contentLeadEntry?: any; // linked_map value
+
+  curationActor?: any;
+  allAccounts?: SubjectInfo;
+};
+
+function withMyAddress<P extends MyAccountProps> (Component: React.ComponentType<P>) {
+  const ResultComponent: React.FunctionComponent<P> = (props: P) => {
+    const {
+      state: { address }
+    } = useMyAccount();
+    const { api } = useContext(ApiContext);
+    const myAccountId = (address && api.isReady)
+      ? api.createType('AccountId', address)
+      : undefined;
+
+    return <Component myAddress={address} myAccountId={myAccountId} {...props} />;
+  };
+
+  ResultComponent.displayName = `withMyAddress(${componentName(Component)})`;
+
+  return ResultComponent;
+}
+
+const withMyMemberIds = withCalls<MyAccountProps>(
+  queryMembershipToProp('memberIdsByRootAccountId', 'myAddress'),
+  queryMembershipToProp('memberIdsByControllerAccountId', 'myAddress')
+);
+
+function withMyMembership<P extends MyAccountProps> (Component: React.ComponentType<P>) {
+  const ResultComponent: React.FunctionComponent<P> = (props: P) => {
+    const { memberIdsByRootAccountId, memberIdsByControllerAccountId } = props;
+
+    const myMemberIdChecked = memberIdsByRootAccountId && memberIdsByControllerAccountId;
+
+    let myMemberId: MemberId | undefined;
+
+    if (memberIdsByRootAccountId && memberIdsByControllerAccountId) {
+      const [memberIdByAccount] = memberIdsByRootAccountId.toArray().concat(memberIdsByControllerAccountId.toArray());
+
+      myMemberId = memberIdByAccount;
+    }
+
+    const iAmMember = myMemberId !== undefined;
+
+    const newProps = {
+      myMemberIdChecked,
+      myMemberId,
+      iAmMember
+    };
+
+    return <Component {...props} {...newProps} />;
+  };
+
+  ResultComponent.displayName = `withMyMembership(${componentName(Component)})`;
+
+  return ResultComponent;
+}
+
+function resolveMyProfile<P extends { myMembership?: Membership | null }> (Component: React.ComponentType<P>) {
+  const ResultComponent: React.FunctionComponent<P> = (props: P) => {
+    let { myMembership } = props;
+
+    myMembership = (!myMembership || myMembership.handle.isEmpty) ? null : myMembership;
+
+    return <Component {...props} myMembership={ myMembership } />;
+  };
+
+  ResultComponent.displayName = `resolveMyProfile(${componentName(Component)})`;
+
+  return ResultComponent;
+}
+
+const withMyProfileCall = withCalls<MyAccountProps>(queryMembershipToProp('membershipById', {
+  paramName: 'myMemberId',
+  propName: 'myMembership'
+}));
+
+const withMyProfile = <P extends MyAccountProps>(Component: React.ComponentType<P>) =>
+  withMulti(Component, withMyProfileCall, resolveMyProfile);
+
+export const withMyAccount = <P extends MyAccountProps>(Component: React.ComponentType<P>) =>
+  withMulti(
+    Component,
+    withObservable(accountObservable.subject, { propName: 'allAccounts' }),
+    withMyAddress,
+    withMyMemberIds,
+    withMyMembership,
+    withMyProfile
+    // withContentWorkingGroup,
+    // withCurationActor
+  );

+ 104 - 0
pioneer/packages/joy-utils/src/react/hocs/guards.tsx

@@ -0,0 +1,104 @@
+import React from 'react';
+import { Message } from 'semantic-ui-react';
+import { Link } from 'react-router-dom';
+
+import { AccountId } from '@polkadot/types/interfaces';
+import { Vec, Option } from '@polkadot/types';
+import { withMulti } from '@polkadot/react-api/index';
+import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
+
+import { MemberId, Membership } from '@joystream/types/members';
+import { LeadId } from '@joystream/types/content-working-group';
+import { useMyMembership } from '../hooks';
+import { componentName } from '../helpers';
+import { withMyAccount } from './accounts';
+
+export type MyAddressProps = {
+  myAddress?: string;
+};
+
+export type MyAccountProps = MyAddressProps & {
+  myAccountId?: AccountId;
+  myMemberId?: MemberId;
+  memberIdsByRootAccountId?: Vec<MemberId>;
+  memberIdsByControllerAccountId?: Vec<MemberId>;
+  myMemberIdChecked?: boolean;
+  iAmMember?: boolean;
+  myMembership?: Membership | null;
+
+  // Content Working Group
+  curatorEntries?: any; // entire linked_map: CuratorId => Curator
+  isLeadSet?: Option<LeadId>;
+  contentLeadId?: LeadId;
+  contentLeadEntry?: any; // linked_map value
+
+  curationActor?: any;
+  allAccounts?: SubjectInfo;
+};
+
+export function MembershipRequired<P extends Record<string, unknown>> (Component: React.ComponentType<P>): React.ComponentType<P> {
+  const ResultComponent: React.FunctionComponent<P> = (props: P) => {
+    const { myMemberIdChecked, iAmMember } = useMyMembership();
+
+    if (!myMemberIdChecked) {
+      return <em>Loading...</em>;
+    } else if (iAmMember) {
+      return <Component {...props} />;
+    }
+
+    return (
+      <Message warning className='JoyMainStatus'>
+        <Message.Header>Only members can access this functionality.</Message.Header>
+        <div style={{ marginTop: '1rem' }}>
+          <Link to={'/members/edit'} className='ui button orange'>
+            Register here
+          </Link>
+          <span style={{ margin: '0 .5rem' }}> or </span>
+          <Link to={'/accounts'} className='ui button'>
+            Change key
+          </Link>
+        </div>
+      </Message>
+    );
+  };
+
+  ResultComponent.displayName = `MembershipRequired(${componentName(Component)})`;
+
+  return ResultComponent;
+}
+
+export function AccountRequired<P extends Record<string, unknown>> (Component: React.ComponentType<P>): React.ComponentType<P> {
+  const ResultComponent: React.FunctionComponent<P> = (props: P) => {
+    const { allAccounts } = useMyMembership();
+
+    if (allAccounts && !Object.keys(allAccounts).length) {
+      return (
+        <Message warning className='JoyMainStatus'>
+          <Message.Header>Please create a key to get started.</Message.Header>
+          <div style={{ marginTop: '1rem' }}>
+            <Link to={'/accounts'} className='ui button orange'>
+              Create key
+            </Link>
+          </div>
+        </Message>
+      );
+    }
+
+    return <Component {...props} />;
+  };
+
+  ResultComponent.displayName = `AccountRequired(${componentName(Component)})`;
+
+  return ResultComponent;
+}
+
+// TODO: We could probably use withAccountRequired, which wouldn't pass any addiotional props, just like withMembershipRequired.
+// Just need to make sure those passed props are not used in the extended components (they probably aren't).
+export const withOnlyAccounts = <P extends MyAccountProps>(Component: React.ComponentType<P>): React.ComponentType<P> =>
+  withMulti(Component, withMyAccount, AccountRequired);
+
+export const withMembershipRequired = <P extends Record<string, unknown>> (Component: React.ComponentType<P>): React.ComponentType<P> =>
+  withMulti(Component, AccountRequired, MembershipRequired);
+
+export const withOnlyMembers = <P extends MyAccountProps>(Component: React.ComponentType<P>): React.ComponentType<P> =>
+  withMulti(Component, withMyAccount, withMembershipRequired);

+ 2 - 0
pioneer/packages/joy-utils/src/react/hooks/index.ts

@@ -0,0 +1,2 @@
+export { default as useMyAccount } from './useMyAccount';
+export { default as useMyMembership } from './useMyMembership';

+ 6 - 0
pioneer/packages/joy-utils/src/react/hooks/useMyAccount.tsx

@@ -0,0 +1,6 @@
+import { useContext } from 'react';
+import { MyAccountContext } from '../context/account';
+
+export default function useMyAccount () {
+  return useContext(MyAccountContext);
+}

+ 6 - 0
pioneer/packages/joy-utils/src/react/hooks/useMyMembership.tsx

@@ -0,0 +1,6 @@
+import { useContext } from 'react';
+import { MyMembershipContext } from '../context';
+
+export default function useMyMembership () {
+  return useContext(MyMembershipContext);
+}

+ 0 - 28
pioneer/packages/old-apps/apps-routing/src/joy-pages.ts

@@ -1,28 +0,0 @@
-import { Routes } from './types';
-
-import { ToS, Privacy } from '@polkadot/joy-pages/index';
-
-export default ([
-  {
-    Component: ToS,
-    display: {
-      isHidden: true
-    },
-    i18n: {
-      defaultValue: 'Terms of Service'
-    },
-    icon: 'file outline',
-    name: 'pages/tos'
-  },
-  {
-    Component: Privacy,
-    display: {
-      isHidden: true
-    },
-    i18n: {
-      defaultValue: 'Privacy Policy'
-    },
-    icon: 'file outline',
-    name: 'pages/privacy'
-  }
-] as Routes);

+ 0 - 17
pioneer/packages/old-apps/apps/src/TopBar.css

@@ -1,17 +0,0 @@
-.JoyTopBar {
-  padding: .5rem .5rem;
-  background-color: #3f3f3f;
-  border-bottom: 1px solid #d4d4d5;
-  text-align: right;
-  margin: 0 -2rem;
-
-  &.NoMyAddress {
-    background-color: #ffeb83;
-    color: #000;
-    text-align: center;
-  }
-
-  .ui--InputAddress {
-    display: inline-block;
-  }
-}

+ 0 - 47
pioneer/packages/old-apps/apps/src/TopBar.tsx

@@ -1,47 +0,0 @@
-import React from 'react';
-// import { Link } from 'react-router-dom';
-import { I18nProps } from '@polkadot/react-components/types';
-import { useMyAccount } from '@polkadot/joy-utils/MyAccountContext';
-import { InputAddress } from '@polkadot/react-components';
-import { Available } from '@polkadot/react-query';
-import translate from './translate';
-import './TopBar.css';
-
-type Props = I18nProps & {};
-
-function renderAddress (address: string) {
-  const balance = <span className="label">Balance: </span>;
-
-  return (
-    <div className="JoyTopBar">
-      <InputAddress
-        defaultValue={address}
-        help="My current key that signs transactions"
-        label="My key"
-        labelExtra={<Available label={balance} params={address} />}
-        type="account"
-      />
-    </div>
-  );
-}
-
-// function renderNoAddress() {
-//   return (
-//     <div className="JoyTopBar NoMyAddress">
-//       <i className="warning sign icon"></i>
-//       <span style={{ marginRight: '1rem' }}>You need to create a key if you want to use all features.</span>
-//       <Link className="ui small button orange" to="/accounts">
-//         Create key
-//       </Link>
-//     </div>
-//   );
-// }
-
-function Component (_props: Props) {
-  const {
-    state: { address }
-  } = useMyAccount();
-  return address ? renderAddress(address) : null;
-}
-
-export default translate(Component);

+ 0 - 11
pioneer/packages/old-apps/react-components/src/styles/old-theme.ts-unused

@@ -1,11 +0,0 @@
-/* Copyright 2017-2019 @polkadot/react-components authors & contributors
-/* This software may be modified and distributed under the terms
-/* of the Apache-2.0 license. See the LICENSE file for details. */
-
-import theme from 'styled-theming';
-
-export const primaryColor = theme('theme', {
-  substrate: '#DB2828',
-  polkadot: '#E6007A',
-  default: 'darkorange'
-});

+ 1 - 1
pioneer/packages/page-staking/src/Targets/Summary.tsx

@@ -66,7 +66,7 @@ function Summary ({ lastReward, numNominators, numValidators, totalStaked }: Pro
           </CardSummary>
         )}
       </section>
-      {numValidators && numNominators && (
+      {numValidators !== undefined && numNominators !== undefined && (
         <CardSummary label={`${t<string>('validators')} / ${t<string>('nominators')}`}>
           {numValidators}&nbsp;/&nbsp;{numNominators}
         </CardSummary>

+ 8 - 0
pioneer/packages/react-components/src/InputAddress/index.tsx

@@ -20,6 +20,8 @@ import Dropdown from '../Dropdown';
 import createHeader from './createHeader';
 import createItem from './createItem';
 
+import { ACCOUNT_CHANGED_EVENT_NAME } from '@polkadot/joy-utils/react/context/account';
+
 interface Props {
   className?: string;
   defaultValue?: Uint8Array | string | null;
@@ -115,6 +117,12 @@ function setLastValue (type: KeyringOption$Type = DEFAULT_TYPE, value: string):
 
   options.defaults[type] = value;
   store.set(STORAGE_KEY, options);
+
+  if (type === 'account') {
+    // This lets us update joy-utils account context in order to always be in sync
+    // with options:InputAddress: { defaults: { account } }) from local storage
+    window.dispatchEvent(new CustomEvent<string>(ACCOUNT_CHANGED_EVENT_NAME, { detail: value }));
+  }
 }
 
 class InputAddress extends React.PureComponent<Props, State> {

+ 5 - 2
pioneer/packages/react-components/src/Tabs/Tab.tsx

@@ -16,16 +16,19 @@ interface Props extends TabItem {
   index: number;
   isSequence?: boolean;
   num: number;
+  forceMatchParams?: boolean;
 }
 
-function Tab ({ basePath, className = '', hasParams, index, isExact, isRoot, isSequence, name, num, text }: Props): React.ReactElement<Props> {
+function Tab ({ basePath, className = '', hasParams, index, isExact, isRoot, isSequence, name, num, text, forceMatchParams }: Props): React.ReactElement<Props> {
   const to = isRoot
     ? basePath
     : `${basePath}/${name}`;
 
   // only do exact matching when not the fallback (first position tab),
   // params are problematic for dynamic hidden such as app-accounts
-  const tabIsExact = isExact || !hasParams || (!isSequence && index === 0);
+  const tabIsExact = forceMatchParams
+    ? false
+    : (isExact || !hasParams || (!isSequence && index === 0));
 
   return (
     <NavLink

+ 1 - 0
pioneer/packages/react-components/src/Tabs/types.ts

@@ -11,4 +11,5 @@ export interface TabItem {
   isRoot?: boolean;
   name: string;
   text: React.ReactNode;
+  forceMatchParams?: boolean;
 }

+ 3 - 1
pioneer/packages/react-components/src/styles/index.ts

@@ -10,12 +10,13 @@ import cssMedia from './media';
 import cssRx from './rx';
 import cssSemantic from './semantic';
 import cssTheme from './theme';
+import cssJoystream from './joystream';
 
 interface Props {
   uiHighlight?: string;
 }
 
-const defaultHighlight = '#f19135'; // #999
+const defaultHighlight = '#4038FF'; // #999
 
 const getHighlight = (props: Props): string =>
   (props.uiHighlight || defaultHighlight);
@@ -281,4 +282,5 @@ export default createGlobalStyle<Props>`
   ${cssMedia}
   ${cssRx}
   ${cssComponents}
+  ${cssJoystream}
 `;

+ 16 - 8
pioneer/packages/old-apps/react-components/src/styles/joystream.ts → pioneer/packages/react-components/src/styles/joystream.ts

@@ -94,14 +94,6 @@ export default css`
     }
   }
 
-  .apps--SideBar-logo {
-    max-height: 26px !important;
-    margin: 1rem 1.5rem 2.5rem 0.75rem !important;
-  }
-  .collapsed .apps--SideBar-logo {
-    margin: 1rem 0.75rem 2.5rem 0.5rem !important;
-  }
-
   .JoyForm {
     margin-bottom: 1.5rem;
 
@@ -192,4 +184,20 @@ export default css`
   .text-blue {
     color: #3b83c0;
   }
+
+  /* Overrides */
+  .ui--IdentityIcon {
+    border: none !important;
+  }
+  /* Normalize SideBar icons width */
+  .apps--SideBar-Item-NavLink svg {
+    width: 20px !important;
+  }
+  /* Fix "collapsed" sidebar on mobile */
+  .apps--Wrapper:not(.menu-open) .apps--SideBar-Scroll {
+    padding: 0 !important;
+  }
+  h1 {
+    text-transform: none;
+  }
 `;

+ 1 - 1
pioneer/packages/react-components/src/styles/theme.ts

@@ -10,7 +10,7 @@ export const colorBtnDefault = '#767778';
 export const colorBtnShadow = '#98999a';
 
 /* highlighted buttons, orange */
-export const colorBtnHighlight = '#f19135';
+export const colorBtnHighlight = '#4038FF';
 
 /* primary buttons, blue */
 export const colorBtnPrimary = colorBtnDefault; // '#2e86ab';

+ 7 - 9
pioneer/tsconfig.json

@@ -4,16 +4,13 @@
     "build/**/*",
     "**/build/**/*",
     "packages/old-apps/**",
-    "packages/joy-members/**/*",
     "packages/joy-election/**/*",
     "packages/joy-forum/**/*",
     "packages/joy-help/**/*",
     "packages/joy-media/**/*",
-    "packages/joy-pages/**/*",
     "packages/joy-proposals/**/*",
     "packages/joy-roles/**/*",
     "packages/joy-settings/**/*",
-    "packages/joy-utils/**/*",
     "packages/joy-utils-old/**/*"
   ],
   "compilerOptions": {
@@ -23,6 +20,7 @@
     "resolveJsonModule": true,
     "baseUrl": ".",
     "paths": {
+      "@polkadot/types/augment": [ "../types/src/definitions/augment-types.ts" ],
       // "@joystream/types/": [ "../types/src/" ],
       // "@joystream/types/*": [ "../types/src/*" ],
       // "@polkadot/joy-election/": [ "packages/joy-election/src/" ],
@@ -33,18 +31,18 @@
       // "@polkadot/joy-help/*": [ "packages/joy-help/src/*" ],
       // "@polkadot/joy-media/": [ "packages/joy-media/src/" ],
       // "@polkadot/joy-media/*": [ "packages/joy-media/src/*" ],
-      // "@polkadot/joy-members/": [ "packages/joy-members/src/" ],
-      // "@polkadot/joy-members/*": [ "packages/joy-members/src/*" ],
-      // "@polkadot/joy-pages/": [ "packages/joy-pages/src/" ],
-      // "@polkadot/joy-pages/*": [ "packages/joy-pages/src/*" ],
+      "@polkadot/joy-members/": [ "packages/joy-members/src/" ],
+      "@polkadot/joy-members/*": [ "packages/joy-members/src/*" ],
+      "@polkadot/joy-pages/": [ "packages/joy-pages/src/" ],
+      "@polkadot/joy-pages/*": [ "packages/joy-pages/src/*" ],
       // "@polkadot/joy-proposals/": [ "packages/joy-proposals/src/" ],
       // "@polkadot/joy-proposals/*": [ "packages/joy-proposals/src/*" ],
       // "@polkadot/joy-roles/": [ "packages/joy-roles/src/" ],
       // "@polkadot/joy-roles/*": [ "packages/joy-roles/src/*" ],
       // "@polkadot/joy-settings/": [ "packages/joy-settings/src/" ],
       // "@polkadot/joy-settings/*": [ "packages/joy-settings/src/*" ],
-      // "@polkadot/joy-utils/": [ "packages/joy-utils/src/" ],
-      // "@polkadot/joy-utils/*": [ "packages/joy-utils/src/*" ],
+      "@polkadot/joy-utils/": [ "packages/joy-utils/src/" ],
+      "@polkadot/joy-utils/*": [ "packages/joy-utils/src/*" ],
       "@polkadot/apps/*": ["packages/apps/src/*"],
       "@polkadot/apps": ["packages/apps/src"],
       "@polkadot/apps-config/*": [ "packages/apps-config/src/*" ],

+ 1 - 3
runtime-modules/proposals/engine/Cargo.toml

@@ -37,6 +37,4 @@ std = [
     'membership/std',
     'stake/std',
     'common/std',
-]
-
-
+]

+ 9 - 9
runtime-modules/service-discovery/src/lib.rs

@@ -10,8 +10,8 @@
 //!
 //! ## Supported extrinsics
 //!
-//! - [set_ipns_id](./struct.Module.html#method.set_ipns_id) - Creates the AccountInfo to save an IPNS identity for the storage provider.
-//! - [unset_ipns_id](./struct.Module.html#method.unset_ipns_id) - Deletes the AccountInfo with the IPNS identity for the storage provider.
+//! - [set_ipns_id](./struct.Module.html#method.set_ipns_id) - Creates the ServiceProviderRecord to save an IPNS identity for the storage provider.
+//! - [unset_ipns_id](./struct.Module.html#method.unset_ipns_id) - Deletes the ServiceProviderRecord with the IPNS identity for the storage provider.
 //! - [set_default_lifetime](./struct.Module.html#method.set_default_lifetime) - Sets default lifetime for storage providers accounts info.
 //! - [set_bootstrap_endpoints](./struct.Module.html#method.set_bootstrap_endpoints) - Sets bootstrap endpoints for the Colossus.
 //!
@@ -63,7 +63,7 @@ pub(crate) const DEFAULT_LIFETIME: u32 = MINIMUM_LIFETIME * 24; // 24hr
 /// Defines the expiration date for the storage provider.
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
 #[derive(Encode, Decode, Default, Clone, PartialEq, Eq)]
-pub struct AccountInfo<BlockNumber> {
+pub struct ServiceProviderRecord<BlockNumber> {
     /// IPNS Identity.
     pub identity: IPNSIdentity,
     /// Block at which information expires.
@@ -81,11 +81,11 @@ decl_storage! {
         /// Bootstrap endpoints maintained by root
         pub BootstrapEndpoints get(fn bootstrap_endpoints): Vec<Url>;
 
-        /// Mapping of service providers' storage provider id to their AccountInfo
+        /// Mapping of service providers' storage provider id to their ServiceProviderRecord
         pub AccountInfoByStorageProviderId get(fn account_info_by_storage_provider_id):
-            map hasher(blake2_128_concat) StorageProviderId<T> => AccountInfo<T::BlockNumber>;
+            map hasher(blake2_128_concat) StorageProviderId<T> => ServiceProviderRecord<T::BlockNumber>;
 
-        /// Lifetime of an AccountInfo record in AccountInfoByAccountId map
+        /// Lifetime of an ServiceProviderRecord record in AccountInfoByAccountId map
         pub DefaultLifetime get(fn default_lifetime) config():
             T::BlockNumber = T::BlockNumber::from(DEFAULT_LIFETIME);
     }
@@ -115,7 +115,7 @@ decl_module! {
         /// Default deposit_event() handler
         fn deposit_event() = default;
 
-        /// Creates the AccountInfo to save an IPNS identity for the storage provider.
+        /// Creates the ServiceProviderRecord to save an IPNS identity for the storage provider.
         /// Requires signed storage provider credentials.
         #[weight = 10_000_000] // TODO: adjust weight
         pub fn set_ipns_id(
@@ -131,7 +131,7 @@ decl_module! {
             // == MUTATION SAFE ==
             //
 
-            <AccountInfoByStorageProviderId<T>>::insert(storage_provider_id, AccountInfo {
+            <AccountInfoByStorageProviderId<T>>::insert(storage_provider_id, ServiceProviderRecord {
                 identity: id.clone(),
                 expires_at: <system::Module<T>>::block_number() + Self::default_lifetime(),
             });
@@ -139,7 +139,7 @@ decl_module! {
             Self::deposit_event(RawEvent::AccountInfoUpdated(storage_provider_id, id));
         }
 
-        /// Deletes the AccountInfo with the IPNS identity for the storage provider.
+        /// Deletes the ServiceProviderRecord with the IPNS identity for the storage provider.
         /// Requires signed storage provider credentials.
         #[weight = 10_000_000] // TODO: adjust weight
         pub fn unset_ipns_id(origin, storage_provider_id: StorageProviderId<T>) {

+ 3 - 3
runtime-modules/service-discovery/src/tests.rs

@@ -27,7 +27,7 @@ fn set_ipns_id() {
         let account_info = Discovery::account_info_by_storage_provider_id(&storage_provider_id);
         assert_eq!(
             account_info,
-            AccountInfo {
+            ServiceProviderRecord {
                 identity: identity.clone(),
                 expires_at: current_block_number + ttl
             }
@@ -70,7 +70,7 @@ fn unset_ipns_id() {
 
         <AccountInfoByStorageProviderId<Test>>::insert(
             &storage_provider_id,
-            AccountInfo {
+            ServiceProviderRecord {
                 expires_at: 1000,
                 identity: "alice".as_bytes().to_vec(),
             },
@@ -120,7 +120,7 @@ fn is_account_info_expired() {
         let id = "alice".as_bytes().to_vec();
         <AccountInfoByStorageProviderId<Test>>::insert(
             &storage_provider_id,
-            AccountInfo {
+            ServiceProviderRecord {
                 expires_at,
                 identity: id.clone(),
             },

+ 0 - 6
runtime/Cargo.toml

@@ -51,9 +51,6 @@ pallet-timestamp = { package = 'pallet-timestamp', default-features = false, git
 pallet-balances = { package = 'pallet-balances', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
 pallet-im-online = { package = 'pallet-im-online', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
 pallet-collective = { package = 'pallet-collective', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
-pallet-contracts = { package = 'pallet-contracts', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
-pallet-contracts-rpc-runtime-api = { package = 'pallet-contracts-rpc-runtime-api', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
-pallet-contracts-primitives = { package = 'pallet-contracts-primitives', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
 
 # Benchmarking
 frame-benchmarking = { git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4', default-features = false, optional = true }
@@ -135,9 +132,6 @@ std = [
     'pallet-im-online/std',
     'pallet-collective/std',
     'pallet-offences/std',
-    'pallet-contracts/std',
-    'pallet-contracts-rpc-runtime-api/std',
-    'pallet-contracts-primitives/std',
 
     # Joystream
     'common/std',

+ 4 - 10
runtime/build.rs

@@ -15,7 +15,7 @@
 // along with Substrate.  If not, see <http://www.gnu.org/licenses/>.
 
 use std::{env, process::Command, string::String};
-use wasm_builder_runner::{WasmBuilder, WasmBuilderSource};
+use wasm_builder_runner::WasmBuilder;
 
 fn main() {
     if !in_real_cargo_environment() {
@@ -23,17 +23,11 @@ fn main() {
         println!("Building DUMMY Wasm binary");
     }
 
-    let file_name = "wasm_binary.rs";
-    let wasm_builder_source = WasmBuilderSource::Crates("1.0.9");
-    // This instructs LLD to export __heap_base as a global variable, which is used by the
-    // external memory allocator.
-    let default_rust_flags = "-Clink-arg=--export=__heap_base";
-
     WasmBuilder::new()
         .with_current_project()
-        .with_wasm_builder_source(wasm_builder_source)
-        .append_to_rust_flags(default_rust_flags)
-        .set_file_name(file_name)
+        .with_wasm_builder_from_crates("1.0.11")
+        .export_heap_base()
+        .import_memory()
         .build()
 }
 

+ 0 - 20
runtime/src/lib.rs

@@ -550,25 +550,6 @@ parameter_types! {
     pub const SurchargeReward: Balance = 0; // no reward
 }
 
-impl pallet_contracts::Trait for Runtime {
-    type Time = Timestamp;
-    type Randomness = RandomnessCollectiveFlip;
-    type Currency = Balances;
-    type Event = Event;
-    type DetermineContractAddress = pallet_contracts::SimpleAddressDeterminer<Runtime>;
-    type TrieIdGenerator = pallet_contracts::TrieIdFromParentCounter<Runtime>;
-    type RentPayment = ();
-    type SignedClaimHandicap = pallet_contracts::DefaultSignedClaimHandicap;
-    type TombstoneDeposit = TombstoneDeposit;
-    type StorageSizeOffset = pallet_contracts::DefaultStorageSizeOffset;
-    type RentByteFee = RentByteFee;
-    type RentDepositOffset = RentDepositOffset;
-    type SurchargeReward = SurchargeReward;
-    type MaxDepth = pallet_contracts::DefaultMaxDepth;
-    type MaxValueSize = pallet_contracts::DefaultMaxValueSize;
-    type WeightPrice = pallet_transaction_payment::Module<Self>;
-}
-
 /// Opaque types. These are used by the CLI to instantiate machinery that don't need to know
 /// the specifics of the runtime. They can then be made to be agnostic over specific formats
 /// of data like extrinsics, allowing for them to continue syncing the network through upgrades
@@ -609,7 +590,6 @@ construct_runtime!(
         Offences: pallet_offences::{Module, Call, Storage, Event},
         RandomnessCollectiveFlip: pallet_randomness_collective_flip::{Module, Call, Storage},
         Sudo: pallet_sudo::{Module, Call, Config<T>, Storage, Event<T>},
-        Contracts: pallet_contracts::{Module, Call, Config, Storage, Event<T>},
         // Joystream
         CouncilElection: election::{Module, Call, Storage, Event<T>, Config<T>},
         Council: council::{Module, Call, Storage, Event<T>, Config<T>},

+ 1 - 39
runtime/src/runtime_api.rs

@@ -1,7 +1,6 @@
 use frame_support::inherent::{CheckInherentsResult, InherentData};
 use frame_support::traits::{KeyOwnerProofSystem, Randomness};
 use frame_support::unsigned::{TransactionSource, TransactionValidity};
-use pallet_contracts_rpc_runtime_api::ContractExecResult;
 use pallet_grandpa::fg_primitives;
 use pallet_transaction_payment_rpc_runtime_api::RuntimeDispatchInfo;
 use sp_api::impl_runtime_apis;
@@ -17,7 +16,7 @@ use crate::{
     GrandpaId, Hash, Index, RuntimeVersion, Signature, VERSION,
 };
 use crate::{
-    AllModules, AuthorityDiscovery, Babe, Call, Contracts, Grandpa, Historical, InherentDataExt,
+    AllModules, AuthorityDiscovery, Babe, Call, Grandpa, Historical, InherentDataExt,
     RandomnessCollectiveFlip, Runtime, SessionKeys, System, TransactionPayment,
 };
 
@@ -182,43 +181,6 @@ impl_runtime_apis! {
         }
     }
 
-
-    impl pallet_contracts_rpc_runtime_api::ContractsApi<Block, AccountId, Balance, BlockNumber>
-    for Runtime
-    {
-        fn call(
-            origin: AccountId,
-            dest: AccountId,
-            value: Balance,
-            gas_limit: u64,
-            input_data: Vec<u8>,
-        ) -> ContractExecResult {
-            let exec_result =
-                Contracts::bare_call(origin, dest, value, gas_limit, input_data);
-            match exec_result {
-                Ok(v) => ContractExecResult::Success {
-                    status: v.status,
-                    data: v.data,
-                },
-                Err(_) => ContractExecResult::Error,
-            }
-        }
-
-        fn get_storage(
-            address: AccountId,
-            key: [u8; 32],
-        ) -> pallet_contracts_primitives::GetStorageResult {
-            Contracts::get_storage(address, key)
-        }
-
-        fn rent_projection(
-            address: AccountId,
-        ) -> pallet_contracts_primitives::RentProjectionResult<BlockNumber> {
-            Contracts::rent_projection(address)
-        }
-    }
-
-
     impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi<
         Block,
         Balance,

+ 1 - 1
runtime/src/tests/storage_integration.rs

@@ -28,7 +28,7 @@ fn storage_provider_helper_succeeds() {
 		let random_provider_result = <StorageProviderHelper as storage::data_directory::StorageProviderHelper<Runtime>>::get_random_storage_provider();
 		assert!(random_provider_result.is_err());
 
-		let account_info = service_discovery::AccountInfo{
+		let account_info = service_discovery::ServiceProviderRecord{
 			identity: Vec::new(),
 			expires_at: 1000
 		};

+ 10 - 23
utils/chain-spec-builder/Cargo.toml

@@ -1,30 +1,17 @@
 [package]
-name = "chain-spec-builder"
-version = "2.1.0"
-authors = ["Parity Technologies <admin@parity.io>"]
-edition = "2018"
-build = "build.rs"
-license = "GPL-3.0"
-homepage = "https://substrate.dev"
-repository = "https://github.com/paritytech/substrate/"
+authors = ['Joystream contributors']
+build = 'build.rs'
+edition = '2018'
+name = 'chain-spec-builder'
+version = '3.0.0'
 
 [dependencies]
 ansi_term = "0.12.1"
 rand = "0.7.2"
-structopt = "0.3.5"
+structopt = "0.3.8"
+sc-keystore = { git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
+sc-telemetry = { git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
+sc-chain-spec = { git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
+sp-core = { git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
 joystream-node = { path = "../../node" }
 
-[dependencies.sr-keystore]
-git = 'https://github.com/paritytech/substrate.git'
-package = 'substrate-keystore'
-rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
-
-[dependencies.sr-primitives]
-git = 'https://github.com/paritytech/substrate.git'
-package = 'substrate-primitives'
-rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
-
-[dependencies.substrate-telemetry]
-git = 'https://github.com/paritytech/substrate.git'
-package = 'substrate-telemetry'
-rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'

+ 15 - 24
utils/chain-spec-builder/src/main.rs

@@ -27,14 +27,15 @@ use joystream_node::{
     chain_spec::{self, chain_spec_properties, AccountId},
     proposals_config,
 };
-use sr_keystore::Store as Keystore;
-use sr_primitives::{
+use sc_chain_spec::ChainType;
+use sc_keystore::Store as Keystore;
+use sc_telemetry::TelemetryEndpoints;
+use sp_core::{
     crypto::{Public, Ss58Codec},
     sr25519,
     traits::BareCryptoStore,
 };
 
-use substrate_telemetry::TelemetryEndpoints;
 const STAGING_TELEMETRY_URL: &str = "wss://telemetry.polkadot.io/submit/";
 
 /// A utility to easily create a testnet chain spec definition with a given set
@@ -134,26 +135,22 @@ fn generate_chain_spec(
     //     "/dns4/tesnet.joystream.org/tcp/30333/p2p/QmaTTdEF6YVCtynSjsXmGPSGcEesAahoZ8pmcCmmBwSE7S",
     // )];
 
+    let telemetry_endpoints = TelemetryEndpoints::new(vec![(STAGING_TELEMETRY_URL.to_string(), 0)])
+        .map_err(|err| format!("Failed to create telemetry endpoints: {:?}", err))?;
+
     let chain_spec = chain_spec::ChainSpec::from_genesis(
         "Joystream Testnet",
         "joy_testnet",
+        ChainType::Development,
         move || genesis_constructor(&authority_seeds, &endowed_accounts, &sudo_account),
-        // below can be manually modified in chainspec file, they don't affect genesis state
-        // but we set some default values here for convenience.
         vec![],
-        Some(TelemetryEndpoints::new(vec![(
-            STAGING_TELEMETRY_URL.to_string(),
-            0,
-        )])),
-        // protocol_id
+        Some(telemetry_endpoints),
         Some(&*"/joy/testnet/0"),
-        // Properties
         Some(chain_spec_properties()),
-        // Extensions
-        None, // Default::default(),
+        None,
     );
 
-    chain_spec.to_json(false).map_err(|err| err)
+    chain_spec.as_json(false).map_err(|err| err)
 }
 
 fn generate_authority_keys_and_store(seeds: &[String], keystore_path: &Path) -> Result<(), String> {
@@ -161,7 +158,7 @@ fn generate_authority_keys_and_store(seeds: &[String], keystore_path: &Path) ->
         let keystore = Keystore::open(keystore_path.join(format!("auth-{}", n)), None)
             .map_err(|err| err.to_string())?;
 
-        let (_, _, grandpa, babe, im_online) = chain_spec::get_authority_keys_from_seed(seed);
+        let (_, _, grandpa, babe, im_online, _) = chain_spec::get_authority_keys_from_seed(seed);
 
         let insert_key = |key_type, public| {
             keystore
@@ -170,17 +167,11 @@ fn generate_authority_keys_and_store(seeds: &[String], keystore_path: &Path) ->
                 .map_err(|_| format!("Failed to insert key: {}", grandpa))
         };
 
-        insert_key(sr_primitives::crypto::key_types::BABE, babe.as_slice())?;
+        insert_key(sp_core::crypto::key_types::BABE, babe.as_slice())?;
 
-        insert_key(
-            sr_primitives::crypto::key_types::GRANDPA,
-            grandpa.as_slice(),
-        )?;
+        insert_key(sp_core::crypto::key_types::GRANDPA, grandpa.as_slice())?;
 
-        insert_key(
-            sr_primitives::crypto::key_types::IM_ONLINE,
-            im_online.as_slice(),
-        )?;
+        insert_key(sp_core::crypto::key_types::IM_ONLINE, im_online.as_slice())?;
     }
 
     Ok(())

+ 272 - 18
yarn.lock

@@ -1374,6 +1374,13 @@
     pirates "^4.0.0"
     source-map-support "^0.5.16"
 
+"@babel/runtime@7.0.0":
+  version "7.0.0"
+  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.0.0.tgz#adeb78fedfc855aa05bc041640f3f6f98e85424c"
+  integrity sha512-7hGhzlcmg01CvH1EHdSPVXYX1aJ8KCEyz6I9xYIi/asDtzBPMyMhVibhM/K6g/5qnKBwjZtp10bNZIEFTRW1MA==
+  dependencies:
+    regenerator-runtime "^0.12.0"
+
 "@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.1", "@babel/runtime@^7.10.3", "@babel/runtime@^7.11.1", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2", "@babel/runtime@^7.9.6":
   version "7.11.2"
   resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.11.2.tgz#f549c13c754cc40b87644b9fa9f09a6a95fe0736"
@@ -4457,6 +4464,13 @@
   resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.4.tgz#15925414e0ad2cd765bfef58842f7e26a7accb24"
   integrity sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==
 
+"@types/query-string@^6.2.0":
+  version "6.3.0"
+  resolved "https://registry.yarnpkg.com/@types/query-string/-/query-string-6.3.0.tgz#b6fa172a01405abcaedac681118e78429d62ea39"
+  integrity sha512-yuIv/WRffRzL7cBW+sla4HwBZrEXRNf1MKQ5SklPEadth+BKbDxiVG8A3iISN5B3yC4EeSCzMZP8llHTcUhOzQ==
+  dependencies:
+    query-string "*"
+
 "@types/reach__router@^1.2.3":
   version "1.3.5"
   resolved "https://registry.yarnpkg.com/@types/reach__router/-/reach__router-1.3.5.tgz#14e1e981cccd3a5e50dc9e969a72de0b9d472f6d"
@@ -4655,16 +4669,37 @@
   dependencies:
     source-map "^0.6.1"
 
-"@types/unist@^2.0.0", "@types/unist@^2.0.2":
+"@types/unist@*", "@types/unist@^2.0.0", "@types/unist@^2.0.2":
   version "2.0.3"
   resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e"
   integrity sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ==
 
+"@types/uuid@^3.4.4":
+  version "3.4.9"
+  resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-3.4.9.tgz#fcf01997bbc9f7c09ae5f91383af076d466594e1"
+  integrity sha512-XDwyIlt/47l2kWLTzw/mtrpLdB+GPSskR2n/PIcPn+VYhVO77rGhRncIR5GPU0KRzXuqkDO+J5qqrG0Y8P6jzQ==
+
 "@types/uuid@^7.0.2":
   version "7.0.4"
   resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-7.0.4.tgz#00a5749810b4ad80bff73a61f9cc9d0d521feb3c"
   integrity sha512-WGZCqBZZ0mXN2RxvLHL6/7RCu+OWs28jgQMP04LWfpyJlQUMTR6YU9CNJAKDgbw+EV/u687INXuLUc7FuML/4g==
 
+"@types/vfile-message@*":
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/@types/vfile-message/-/vfile-message-2.0.0.tgz#690e46af0fdfc1f9faae00cd049cc888957927d5"
+  integrity sha512-GpTIuDpb9u4zIO165fUy9+fXcULdD8HFRNli04GehoMVbeNq7D6OBnqSmg3lxZnC+UvgUhEWKxdKiwYUkGltIw==
+  dependencies:
+    vfile-message "*"
+
+"@types/vfile@^3.0.0":
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/@types/vfile/-/vfile-3.0.2.tgz#19c18cd232df11ce6fa6ad80259bc86c366b09b9"
+  integrity sha512-b3nLFGaGkJ9rzOcuXRfHkZMdjsawuDD0ENL9fzTophtBg8FJHSGbH7daXkEpcwy3v7Xol3pAvsmlYyFhR4pqJw==
+  dependencies:
+    "@types/node" "*"
+    "@types/unist" "*"
+    "@types/vfile-message" "*"
+
 "@types/vfile@^4.0.0":
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/@types/vfile/-/vfile-4.0.0.tgz#c32d13cbda319bc9f4ab3cacc0263b4ba1dd1ea3"
@@ -4715,6 +4750,11 @@
   resolved "https://registry.yarnpkg.com/@types/yoga-layout/-/yoga-layout-1.9.2.tgz#efaf9e991a7390dc081a0b679185979a83a9639a"
   integrity sha512-S9q47ByT2pPvD65IvrWp7qppVMpk9WGMbVq9wbWZOHg6tnXSD4vyhao6nOSBwwfDdV2p3Kx9evA9vI+XWTfDvw==
 
+"@types/yup@^0.26.10":
+  version "0.26.37"
+  resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.26.37.tgz#7a52854ac602ba0dc969bebc960559f7464a1686"
+  integrity sha512-cDqR/ez4+iAVQYOwadXjKX4Dq1frtnDGV2GNVKj3aUVKVCKRvsr8esFk66j+LgeeJGmrMcBkkfCf3zk13MjV7A==
+
 "@typescript-eslint/eslint-plugin@3.8.0":
   version "3.8.0"
   resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.8.0.tgz#f82947bcdd9a4e42be7ad80dfd61f1dc411dd1df"
@@ -5902,7 +5942,7 @@ arrify@^2.0.1:
   resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa"
   integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==
 
-asap@^2.0.0:
+asap@^2.0.0, asap@~2.0.3:
   version "2.0.6"
   resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
   integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=
@@ -8515,6 +8555,11 @@ core-js-pure@^3.0.1:
   resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.4.5.tgz#284474cb48d134e26e6e314cb89986c6c59480fb"
   integrity sha512-v3BoUOhmBvs4Z17jG/oM7qyv+tEEMvD1FYDDfxa6uD5W2rA/DpKvhvmyrBzxuMQTa/91UQKisaiqe0+0GuL2oA==
 
+core-js@^1.0.0:
+  version "1.2.7"
+  resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636"
+  integrity sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=
+
 core-js@^2.4.0, core-js@^2.6.5:
   version "2.6.10"
   resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.10.tgz#8a5b8391f8cc7013da703411ce5b585706300d7f"
@@ -8668,6 +8713,14 @@ create-react-context@0.3.0, create-react-context@^0.3.0:
     gud "^1.0.0"
     warning "^4.0.3"
 
+create-react-context@^0.2.2:
+  version "0.2.3"
+  resolved "https://registry.yarnpkg.com/create-react-context/-/create-react-context-0.2.3.tgz#9ec140a6914a22ef04b8b09b7771de89567cb6f3"
+  integrity sha512-CQBmD0+QGgTaxDL3OX1IDXYqjkp2It4RIbcb99jS6AEg27Ga+a9G3JtK6SIu0HBwPLZlmwt9F7UwWA4Bn92Rag==
+  dependencies:
+    fbjs "^0.8.0"
+    gud "^1.0.0"
+
 cross-spawn@6.0.5, cross-spawn@^6.0.0, cross-spawn@^6.0.5:
   version "6.0.5"
   resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
@@ -9230,6 +9283,11 @@ deepmerge@^1.5.2:
   resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-1.5.2.tgz#10499d868844cdad4fee0842df8c7f6f0c95a753"
   integrity sha512-95k0GDqvBjZavkuvzx/YqVLv/6YYa17fz6ILMSf7neqQITCPbnfEnQvEgMPNjH4kgobe7+WIL0yJEHku+H3qtQ==
 
+deepmerge@^2.1.1:
+  version "2.2.1"
+  resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.2.1.tgz#5d3ff22a01c00f645405a2fbc17d0778a1801170"
+  integrity sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==
+
 deepmerge@^4.0.0, deepmerge@^4.2.2:
   version "4.2.2"
   resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955"
@@ -11296,6 +11354,19 @@ fb-watchman@^2.0.0:
   dependencies:
     bser "^2.0.0"
 
+fbjs@^0.8.0:
+  version "0.8.17"
+  resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd"
+  integrity sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=
+  dependencies:
+    core-js "^1.0.0"
+    isomorphic-fetch "^2.1.1"
+    loose-envify "^1.0.0"
+    object-assign "^4.1.0"
+    promise "^7.1.1"
+    setimmediate "^1.0.5"
+    ua-parser-js "^0.7.18"
+
 fd-slicer@~1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e"
@@ -11501,7 +11572,7 @@ find-babel-config@^1.2.0:
     json5 "^0.5.1"
     path-exists "^3.0.0"
 
-find-cache-dir@^2.0.0, find-cache-dir@^2.1.0:
+find-cache-dir@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7"
   integrity sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==
@@ -11643,6 +11714,11 @@ flush-write-stream@^1.0.0, flush-write-stream@^1.0.2:
     inherits "^2.0.3"
     readable-stream "^2.3.6"
 
+fn-name@~2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/fn-name/-/fn-name-2.0.1.tgz#5214d7537a4d06a4a301c0cc262feb84188002e7"
+  integrity sha1-UhTXU3pNBqSjAcDMJi/rhBiAAuc=
+
 focus-lock@^0.7.0:
   version "0.7.0"
   resolved "https://registry.yarnpkg.com/focus-lock/-/focus-lock-0.7.0.tgz#b2bfb0ca7beacc8710a1ff74275fe0dc60a1d88a"
@@ -11744,6 +11820,21 @@ formidable@^1.2.0:
   resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.2.2.tgz#bf69aea2972982675f00865342b982986f6b8dd9"
   integrity sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q==
 
+formik@^1.5.0:
+  version "1.5.8"
+  resolved "https://registry.yarnpkg.com/formik/-/formik-1.5.8.tgz#eee8cd345effe46839bc748c7f920486f12f14b0"
+  integrity sha512-fNvPe+ddbh+7xiByT25vuso2p2hseG/Yvuj211fV1DbCjljUEG9OpgRpcb7g7O3kxHX/q31cbZDzMxJXPWSNwA==
+  dependencies:
+    create-react-context "^0.2.2"
+    deepmerge "^2.1.1"
+    hoist-non-react-statics "^3.3.0"
+    lodash "^4.17.14"
+    lodash-es "^4.17.14"
+    prop-types "^15.6.1"
+    react-fast-compare "^2.0.1"
+    tiny-warning "^1.0.2"
+    tslib "^1.9.3"
+
 forwarded@~0.1.2:
   version "0.1.2"
   resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
@@ -14746,7 +14837,7 @@ isobject@^4.0.0:
   resolved "https://registry.yarnpkg.com/isobject/-/isobject-4.0.0.tgz#3f1c9155e73b192022a80819bacd0343711697b0"
   integrity sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA==
 
-isomorphic-fetch@^2.2.1:
+isomorphic-fetch@^2.1.1, isomorphic-fetch@^2.2.1:
   version "2.2.1"
   resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9"
   integrity sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=
@@ -16129,6 +16220,11 @@ locate-path@^5.0.0:
   dependencies:
     p-locate "^4.1.0"
 
+lodash-es@^4.17.14:
+  version "4.17.15"
+  resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78"
+  integrity sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ==
+
 lodash._reinterpolate@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
@@ -16605,6 +16701,11 @@ markdown-loader@^5.1.0:
     loader-utils "^1.2.3"
     marked "^0.7.0"
 
+markdown-table@^1.1.0:
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-1.1.3.tgz#9fcb69bcfdb8717bfd0398c6ec2d93036ef8de60"
+  integrity sha512-1RUZVgQlpJSPWYbFSpmudq5nHY1doEIv89gBtF0s4gW1GF2XorxcA/70M5vq7rLv0a6mhOUccRsqkwhwLCIQ2Q==
+
 markdown-table@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-2.0.0.tgz#194a90ced26d31fe753d8b9434430214c011865b"
@@ -16668,6 +16769,13 @@ mdast-add-list-metadata@1.0.1:
   dependencies:
     unist-util-visit-parents "1.1.2"
 
+mdast-util-compact@^1.0.0:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/mdast-util-compact/-/mdast-util-compact-1.0.4.tgz#d531bb7667b5123abf20859be086c4d06c894593"
+  integrity sha512-3YDMQHI5vRiS2uygEFYaqckibpJtKq5Sj2c8JioeOQBU6INpKbdWzfyLqFFnDwEcEnRFIdMsguzs5pC1Jp4Isg==
+  dependencies:
+    unist-util-visit "^1.1.0"
+
 mdast-util-compact@^2.0.0:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/mdast-util-compact/-/mdast-util-compact-2.0.1.tgz#cabc69a2f43103628326f35b1acf735d55c99490"
@@ -18746,7 +18854,7 @@ parse-asn1@^5.0.0:
     pbkdf2 "^3.0.3"
     safe-buffer "^5.1.1"
 
-parse-entities@^1.1.0, parse-entities@^1.1.2:
+parse-entities@^1.0.2, parse-entities@^1.1.0, parse-entities@^1.1.2:
   version "1.2.2"
   resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-1.2.2.tgz#c31bf0f653b6661354f8973559cb86dd1d5edf50"
   integrity sha512-NzfpbxW/NPrzZ/yYSoQxyqUZMZXIdCfE0OIN4ESsnptHJECoUk3FZktxNuzQf4tjt5UEopnxpYJbvYuxIFDdsg==
@@ -19079,7 +19187,7 @@ pirates@^3.0.2:
   dependencies:
     node-modules-regexp "^1.0.0"
 
-pirates@^4.0.0, pirates@^4.0.1:
+pirates@^4.0.1:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.1.tgz#643a92caf894566f91b2b986d2c66950a8e2fb87"
   integrity sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==
@@ -19828,6 +19936,13 @@ promise.prototype.finally@^3.1.0:
     es-abstract "^1.17.0-next.0"
     function-bind "^1.1.1"
 
+promise@^7.1.1:
+  version "7.3.1"
+  resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf"
+  integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==
+  dependencies:
+    asap "~2.0.3"
+
 promise@~1.3.0:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/promise/-/promise-1.3.0.tgz#e5cc9a4c8278e4664ffedc01c7da84842b040175"
@@ -19873,6 +19988,11 @@ proper-lockfile@^4.0.0, proper-lockfile@^4.1.1:
     retry "^0.12.0"
     signal-exit "^3.0.2"
 
+property-expr@^1.5.0:
+  version "1.5.1"
+  resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-1.5.1.tgz#22e8706894a0c8e28d58735804f6ba3a3673314f"
+  integrity sha512-CGuc0VUTGthpJXL36ydB6jnbyOf/rAHFvmVrJlH+Rg0DqqLFQGAP6hIaxD/G0OAmBJPhXDHuEJigrp0e0wFV6g==
+
 property-information@^5.0.0:
   version "5.5.0"
   resolved "https://registry.yarnpkg.com/property-information/-/property-information-5.5.0.tgz#4dc075d493061a82e2b7d096f406e076ed859943"
@@ -20142,6 +20262,15 @@ qs@~6.5.2:
   resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
   integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
 
+query-string@*, query-string@^6.13.1, query-string@^6.2.0:
+  version "6.13.1"
+  resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.13.1.tgz#d913ccfce3b4b3a713989fe6d39466d92e71ccad"
+  integrity sha512-RfoButmcK+yCta1+FuU8REvisx1oEzhMKwhLUNcepQTPGcNMp1sIqjnfCtfnvGSQZQEhaBHvccujtWoUV3TTbA==
+  dependencies:
+    decode-uri-component "^0.2.0"
+    split-on-first "^1.0.0"
+    strict-uri-encode "^2.0.0"
+
 query-string@^4.1.0:
   version "4.3.4"
   resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb"
@@ -20159,15 +20288,6 @@ query-string@^5.0.1:
     object-assign "^4.1.0"
     strict-uri-encode "^1.0.0"
 
-query-string@^6.13.1:
-  version "6.13.1"
-  resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.13.1.tgz#d913ccfce3b4b3a713989fe6d39466d92e71ccad"
-  integrity sha512-RfoButmcK+yCta1+FuU8REvisx1oEzhMKwhLUNcepQTPGcNMp1sIqjnfCtfnvGSQZQEhaBHvccujtWoUV3TTbA==
-  dependencies:
-    decode-uri-component "^0.2.0"
-    split-on-first "^1.0.0"
-    strict-uri-encode "^2.0.0"
-
 querystring-es3@^0.2.0, querystring-es3@^0.2.1:
   version "0.2.1"
   resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73"
@@ -20403,6 +20523,11 @@ react-error-overlay@^6.0.3:
   resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.7.tgz#1dcfb459ab671d53f660a991513cb2f0a0553108"
   integrity sha512-TAv1KJFh3RhqxNvhzxj6LeT5NWklP6rDr2a0jaTfsZ5wSZWHOGeqQyejUp3xxLfPt2UpyJEcVQB/zyPcmonNFA==
 
+react-fast-compare@^2.0.1:
+  version "2.0.4"
+  resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9"
+  integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==
+
 react-fast-compare@^3.0.1:
   version "3.2.0"
   resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb"
@@ -20477,7 +20602,7 @@ react-lifecycles-compat@^3.0.4:
   resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
   integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
 
-react-markdown@^4.3.1:
+react-markdown@^4.0.6, react-markdown@^4.3.1:
   version "4.3.1"
   resolved "https://registry.yarnpkg.com/react-markdown/-/react-markdown-4.3.1.tgz#39f0633b94a027445b86c9811142d05381300f2f"
   integrity sha512-HQlWFTbDxTtNY6bjgp3C3uv1h2xcjCSi1zAEzfBW9OwJJvENSYiLXWNXN5hHLsoqai7RnZiiHzcnWdXk2Splzw==
@@ -21003,6 +21128,11 @@ regenerator-runtime@^0.11.0:
   resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
   integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==
 
+regenerator-runtime@^0.12.0:
+  version "0.12.1"
+  resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de"
+  integrity sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==
+
 regenerator-runtime@^0.13.2:
   version "0.13.3"
   resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz#7cf6a77d8f5c6f60eb73c5fc1955b2ceb01e6bf5"
@@ -21144,6 +21274,27 @@ remark-parse@^5.0.0:
     vfile-location "^2.0.0"
     xtend "^4.0.1"
 
+remark-parse@^6.0.0:
+  version "6.0.3"
+  resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-6.0.3.tgz#c99131052809da482108413f87b0ee7f52180a3a"
+  integrity sha512-QbDXWN4HfKTUC0hHa4teU463KclLAnwpn/FBn87j9cKYJWWawbiLgMfP2Q4XwhxxuuuOxHlw+pSN0OKuJwyVvg==
+  dependencies:
+    collapse-white-space "^1.0.2"
+    is-alphabetical "^1.0.0"
+    is-decimal "^1.0.0"
+    is-whitespace-character "^1.0.0"
+    is-word-character "^1.0.0"
+    markdown-escapes "^1.0.0"
+    parse-entities "^1.1.0"
+    repeat-string "^1.5.4"
+    state-toggle "^1.0.0"
+    trim "0.0.1"
+    trim-trailing-lines "^1.0.0"
+    unherit "^1.0.4"
+    unist-util-remove-position "^1.0.0"
+    vfile-location "^2.0.0"
+    xtend "^4.0.1"
+
 remark-parse@^8.0.0:
   version "8.0.3"
   resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-8.0.3.tgz#9c62aa3b35b79a486454c690472906075f40c7e1"
@@ -21166,6 +21317,26 @@ remark-parse@^8.0.0:
     vfile-location "^3.0.0"
     xtend "^4.0.1"
 
+remark-stringify@^6.0.0:
+  version "6.0.4"
+  resolved "https://registry.yarnpkg.com/remark-stringify/-/remark-stringify-6.0.4.tgz#16ac229d4d1593249018663c7bddf28aafc4e088"
+  integrity sha512-eRWGdEPMVudijE/psbIDNcnJLRVx3xhfuEsTDGgH4GsFF91dVhw5nhmnBppafJ7+NWINW6C7ZwWbi30ImJzqWg==
+  dependencies:
+    ccount "^1.0.0"
+    is-alphanumeric "^1.0.0"
+    is-decimal "^1.0.0"
+    is-whitespace-character "^1.0.0"
+    longest-streak "^2.0.1"
+    markdown-escapes "^1.0.0"
+    markdown-table "^1.1.0"
+    mdast-util-compact "^1.0.0"
+    parse-entities "^1.0.2"
+    repeat-string "^1.5.4"
+    state-toggle "^1.0.0"
+    stringify-entities "^1.0.1"
+    unherit "^1.0.4"
+    xtend "^4.0.1"
+
 remark-stringify@^8.0.0:
   version "8.1.1"
   resolved "https://registry.yarnpkg.com/remark-stringify/-/remark-stringify-8.1.1.tgz#e2a9dc7a7bf44e46a155ec78996db896780d8ce5"
@@ -21186,6 +21357,15 @@ remark-stringify@^8.0.0:
     unherit "^1.0.4"
     xtend "^4.0.1"
 
+remark@^10.0.1:
+  version "10.0.1"
+  resolved "https://registry.yarnpkg.com/remark/-/remark-10.0.1.tgz#3058076dc41781bf505d8978c291485fe47667df"
+  integrity sha512-E6lMuoLIy2TyiokHprMjcWNJ5UxfGQjaMSMhV+f4idM625UjjK4j798+gPs5mfjzDE6vL0oFKVeZM6gZVSVrzQ==
+  dependencies:
+    remark-parse "^6.0.0"
+    remark-stringify "^6.0.0"
+    unified "^7.0.0"
+
 remark@^12.0.0:
   version "12.0.1"
   resolved "https://registry.yarnpkg.com/remark/-/remark-12.0.1.tgz#f1ddf68db7be71ca2bad0a33cd3678b86b9c709f"
@@ -21982,7 +22162,7 @@ set-value@^2.0.0, set-value@^2.0.1:
     is-plain-object "^2.0.3"
     split-string "^3.0.1"
 
-setimmediate@^1.0.4:
+setimmediate@^1.0.4, setimmediate@^1.0.5:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
   integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=
@@ -22888,6 +23068,16 @@ string_decoder@~1.1.1:
   dependencies:
     safe-buffer "~5.1.0"
 
+stringify-entities@^1.0.1:
+  version "1.3.2"
+  resolved "https://registry.yarnpkg.com/stringify-entities/-/stringify-entities-1.3.2.tgz#a98417e5471fd227b3e45d3db1861c11caf668f7"
+  integrity sha512-nrBAQClJAPN2p+uGCVJRPIPakKeKWZ9GtBCmormE7pWOSlHat7+x5A8gx85M7HM5Dt0BP3pP5RhVW77WdbJJ3A==
+  dependencies:
+    character-entities-html4 "^1.0.0"
+    character-entities-legacy "^1.0.0"
+    is-alphanumerical "^1.0.0"
+    is-hexadecimal "^1.0.0"
+
 stringify-entities@^3.0.0:
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/stringify-entities/-/stringify-entities-3.0.1.tgz#32154b91286ab0869ab2c07696223bd23b6dbfc0"
@@ -23002,6 +23192,11 @@ strip-json-comments@~2.0.1:
   resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
   integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
 
+strip-markdown@^3.0.3:
+  version "3.1.2"
+  resolved "https://registry.yarnpkg.com/strip-markdown/-/strip-markdown-3.1.2.tgz#172f6f89f9a98896e65a65422e0507f2bbac1667"
+  integrity sha512-NjwW6CEefesmHQPs7lof/lgnSriqUnRNOWpnrNPq9A7/yOCdnhaB7DcxlhYuN7WiiRUe349aitAsTQ/ajM9Dmw==
+
 strip-outer@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/strip-outer/-/strip-outer-1.0.1.tgz#b2fd2abf6604b9d1e6013057195df836b8a9d631"
@@ -23307,6 +23502,11 @@ symbol.prototype.description@^1.0.0:
     es-abstract "^1.17.0-next.1"
     has-symbols "^1.0.1"
 
+synchronous-promise@^2.0.5:
+  version "2.0.13"
+  resolved "https://registry.yarnpkg.com/synchronous-promise/-/synchronous-promise-2.0.13.tgz#9d8c165ddee69c5a6542862b405bc50095926702"
+  integrity sha512-R9N6uDkVsghHePKh1TEqbnLddO2IY25OcsksyFp/qBe7XYd0PVbKEWxhcdMhpLzE1I6skj5l4aEZ3CRxcbArlA==
+
 table@^5.2.3, table@^5.4.6:
   version "5.4.6"
   resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e"
@@ -23765,7 +23965,7 @@ tiny-invariant@^1.0.2, tiny-invariant@^1.0.6:
   resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875"
   integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==
 
-tiny-warning@^1.0.0, tiny-warning@^1.0.3:
+tiny-warning@^1.0.0, tiny-warning@^1.0.2, tiny-warning@^1.0.3:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
   integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
@@ -23893,6 +24093,11 @@ toposort@^1.0.0:
   resolved "https://registry.yarnpkg.com/toposort/-/toposort-1.0.7.tgz#2e68442d9f64ec720b8cc89e6443ac6caa950029"
   integrity sha1-LmhELZ9k7HILjMieZEOsbKqVACk=
 
+toposort@^2.0.2:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330"
+  integrity sha1-riF2gXXRVZ1IvvNUILL0li8JwzA=
+
 touch@^3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b"
@@ -24250,6 +24455,11 @@ typescript@^3.0.3, typescript@^3.7.2, typescript@^3.7.5, typescript@^3.8.3, type
   resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa"
   integrity sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==
 
+ua-parser-js@^0.7.18:
+  version "0.7.21"
+  resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.21.tgz#853cf9ce93f642f67174273cc34565ae6f308777"
+  integrity sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ==
+
 uc.micro@^1.0.1, uc.micro@^1.0.5:
   version "1.0.6"
   resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac"
@@ -24354,6 +24564,20 @@ unified@^6.1.5:
     vfile "^2.0.0"
     x-is-string "^0.1.0"
 
+unified@^7.0.0:
+  version "7.1.0"
+  resolved "https://registry.yarnpkg.com/unified/-/unified-7.1.0.tgz#5032f1c1ee3364bd09da12e27fdd4a7553c7be13"
+  integrity sha512-lbk82UOIGuCEsZhPj8rNAkXSDXd6p0QLzIuSsCdxrqnqU56St4eyOB+AlXsVgVeRmetPTYydIuvFfpDIed8mqw==
+  dependencies:
+    "@types/unist" "^2.0.0"
+    "@types/vfile" "^3.0.0"
+    bail "^1.0.0"
+    extend "^3.0.0"
+    is-plain-obj "^1.1.0"
+    trough "^1.0.0"
+    vfile "^3.0.0"
+    x-is-string "^0.1.0"
+
 unified@^9.0.0:
   version "9.1.0"
   resolved "https://registry.yarnpkg.com/unified/-/unified-9.1.0.tgz#7ba82e5db4740c47a04e688a9ca8335980547410"
@@ -24872,6 +25096,14 @@ vfile-location@^3.0.0:
   resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-3.0.1.tgz#d78677c3546de0f7cd977544c367266764d31bb3"
   integrity sha512-yYBO06eeN/Ki6Kh1QAkgzYpWT1d3Qln+ZCtSbJqFExPl1S3y2qqotJQXoh6qEvl/jDlgpUJolBn3PItVnnZRqQ==
 
+vfile-message@*:
+  version "2.0.4"
+  resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-2.0.4.tgz#5b43b88171d409eae58477d13f23dd41d52c371a"
+  integrity sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==
+  dependencies:
+    "@types/unist" "^2.0.0"
+    unist-util-stringify-position "^2.0.0"
+
 vfile-message@^1.0.0:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-1.1.1.tgz#5833ae078a1dfa2d96e9647886cd32993ab313e1"
@@ -24908,6 +25140,16 @@ vfile@^2.0.0:
     unist-util-stringify-position "^1.0.0"
     vfile-message "^1.0.0"
 
+vfile@^3.0.0:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/vfile/-/vfile-3.0.1.tgz#47331d2abe3282424f4a4bb6acd20a44c4121803"
+  integrity sha512-y7Y3gH9BsUSdD4KzHsuMaCzRjglXN0W2EcMf0gpvu6+SbsGhMje7xDc8AEoeXy6mIwCKMI6BkjMsRjzQbhMEjQ==
+  dependencies:
+    is-buffer "^2.0.0"
+    replace-ext "1.0.0"
+    unist-util-stringify-position "^1.0.0"
+    vfile-message "^1.0.0"
+
 vinyl-fs@^3.0.1:
   version "3.0.3"
   resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-3.0.3.tgz#c85849405f67428feabbbd5c5dbdd64f47d31bc7"
@@ -25911,6 +26153,18 @@ yoga-layout-prebuilt@^1.9.3:
   dependencies:
     "@types/yoga-layout" "1.9.2"
 
+yup@^0.26.10:
+  version "0.26.10"
+  resolved "https://registry.yarnpkg.com/yup/-/yup-0.26.10.tgz#3545839663289038faf25facfc07e11fd67c0cb1"
+  integrity sha512-keuNEbNSnsOTOuGCt3UJW69jDE3O4P+UHAakO7vSeFMnjaitcmlbij/a3oNb9g1Y1KvSKH/7O1R2PQ4m4TRylw==
+  dependencies:
+    "@babel/runtime" "7.0.0"
+    fn-name "~2.0.1"
+    lodash "^4.17.10"
+    property-expr "^1.5.0"
+    synchronous-promise "^2.0.5"
+    toposort "^2.0.2"
+
 zepto@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/zepto/-/zepto-1.2.0.tgz#e127bd9e66fd846be5eab48c1394882f7c0e4f98"