From 59117a3c112890540fe67a1e404e4b02224e7fbe Mon Sep 17 00:00:00 2001 From: epiqueras Date: Thu, 26 Apr 2018 10:29:40 -0700 Subject: [PATCH 1/5] feat(joyride): set up --- package.json | 1 + src/bootstrap/app.scss | 1 + src/components/page-not-found/index.js | 4 +- src/components/slider/index.js | 4 +- .../iico/components/joyride/index.js | 26 ++++++ .../iico/components/joyride/steps.js | 1 + src/containers/iico/index.js | 86 ++++++++++--------- yarn.lock | 25 +++++- 8 files changed, 101 insertions(+), 47 deletions(-) create mode 100644 src/containers/iico/components/joyride/index.js create mode 100644 src/containers/iico/components/joyride/steps.js diff --git a/package.json b/package.json index 0e42b93..32bf3e0 100755 --- a/package.json +++ b/package.json @@ -80,6 +80,7 @@ "react-blockies": "^1.2.2", "react-dom": "^16.2.0", "react-helmet": "^5.2.0", + "react-joyride": "^1.11.4", "react-minimal-pie-chart": "^3.0.1", "react-redux": "^5.0.6", "react-redux-toastr": "^7.2.3", diff --git a/src/bootstrap/app.scss b/src/bootstrap/app.scss index f50cbc3..1dce8e7 100755 --- a/src/bootstrap/app.scss +++ b/src/bootstrap/app.scss @@ -1,6 +1,7 @@ @import '~normalize.css'; @import '~react-redux-toastr/lib/css/react-redux-toastr.min.css'; @import '~create-redux-form/animations/carousel.css'; +@import '~react-joyride/lib/react-joyride'; @import '../styles/_colors.scss'; @import '../styles/_typography.scss'; @import '../styles/_toastr.scss'; diff --git a/src/components/page-not-found/index.js b/src/components/page-not-found/index.js index d1a2b81..db55e6e 100755 --- a/src/components/page-not-found/index.js +++ b/src/components/page-not-found/index.js @@ -2,12 +2,10 @@ import React from 'react' import './page-not-found.css' -const PageNotFound = () => ( +export default () => (
404. This is not the page you are looking for.
) - -export default PageNotFound diff --git a/src/components/slider/index.js b/src/components/slider/index.js index 9dafa63..80bb125 100644 --- a/src/components/slider/index.js +++ b/src/components/slider/index.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types' import './slider.css' -class Slider extends PureComponent { +export default class Slider extends PureComponent { static propTypes = { // State startLabel: PropTypes.string.isRequired, @@ -105,5 +105,3 @@ class Slider extends PureComponent { ) } } - -export default Slider diff --git a/src/containers/iico/components/joyride/index.js b/src/containers/iico/components/joyride/index.js new file mode 100644 index 0000000..41ec080 --- /dev/null +++ b/src/containers/iico/components/joyride/index.js @@ -0,0 +1,26 @@ +import React from 'react' +import PropTypes from 'prop-types' +import ReactJoyride from 'react-joyride' + +import steps from './steps' + +const Joyride = ({ children, getRef }) => ( + + {children} + +) + +Joyride.propTypes = { + // State + children: PropTypes.node.isRequired, + + // Callbacks + getRef: PropTypes.func.isRequired +} + +export default Joyride diff --git a/src/containers/iico/components/joyride/steps.js b/src/containers/iico/components/joyride/steps.js new file mode 100644 index 0000000..9859f07 --- /dev/null +++ b/src/containers/iico/components/joyride/steps.js @@ -0,0 +1 @@ +export default [] diff --git a/src/containers/iico/index.js b/src/containers/iico/index.js index cc6b5e0..d17667d 100644 --- a/src/containers/iico/index.js +++ b/src/containers/iico/index.js @@ -9,6 +9,7 @@ import * as IICOActions from '../../actions/iico' import Data from './components/data' import Bids from './components/bids' +import Joyride from './components/joyride' import './iico.css' @@ -30,6 +31,9 @@ class IICO extends PureComponent { fetchIICOBids: PropTypes.func.isRequired } + pollIICODataInterval = null + joyrideRef = null + componentDidMount() { const { match: { params: { address } }, @@ -47,52 +51,54 @@ class IICO extends PureComponent { clearInterval(this.pollIICODataInterval) } + getJoyrideRef = ref => (this.joyrideRef = ref) + render() { const { match: { params: { address } }, IICOData, IICOBids } = this.props return (
- - } - done={ - IICOData.data && ( -
-
- -
-
- } - done={ - IICOBids.data && ( - - ) - } - failedLoading="There was an error fetching your bids." - /> + + + } + done={ + IICOData.data && ( +
+
+ +
+
+ } + done={ + IICOBids.data && ( + + ) + } + failedLoading="There was an error fetching your bids." + /> +
-
- ) - } - failedLoading="The address or the contract it holds is invalid. Try another one." - /> - {/* TODO: Render './components/submit-bid-form' and disable submit button if already participated */} - {/* TODO: Render withdraw button if already participated and in first period */} + ) + } + failedLoading="The address or the contract it holds is invalid. Try another one." + /> +
) } diff --git a/yarn.lock b/yarn.lock index a5f47e4..54339cb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5557,7 +5557,7 @@ global-prefix@^1.0.1: is-windows "^1.0.1" which "^1.2.14" -global@^4.3.2: +global@^4.3.2, global@~4.3.0: version "4.3.2" resolved "https://registry.yarnpkg.com/global/-/global-4.3.2.tgz#e76989268a6c74c38908b1305b10fc0e394e9d0f" dependencies: @@ -8274,6 +8274,10 @@ neo-async@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.5.0.tgz#76b1c823130cca26acfbaccc8fbaf0a2fa33b18f" +nested-property@^0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/nested-property/-/nested-property-0.0.7.tgz#ff222f233ca8793c6828b4117091bea597130f4f" + next-tick@1: version "1.0.0" resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" @@ -9723,6 +9727,12 @@ raf@3.4.0, raf@^3.4.0: dependencies: performance-now "^2.1.0" +rafl@~1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/rafl/-/rafl-1.2.2.tgz#fe930f758211020d47e38815f5196a8be4150740" + dependencies: + global "~4.3.0" + railroad-diagrams@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz#eb7e6267548ddedfb899c1b90e57374559cddb7e" @@ -9916,6 +9926,13 @@ react-inspector@^2.2.2: babel-runtime "^6.26.0" is-dom "^1.0.9" +react-joyride@^1.11.4: + version "1.11.4" + resolved "https://registry.yarnpkg.com/react-joyride/-/react-joyride-1.11.4.tgz#974369da82ac5c2d563f754f13bed1210f18cb3c" + dependencies: + nested-property "^0.0.7" + scroll "^2.0.3" + react-json-view@^1.13.3: version "1.16.1" resolved "https://registry.yarnpkg.com/react-json-view/-/react-json-view-1.16.1.tgz#ab81fc7db5af70135248704e37741cf097f46604" @@ -10887,6 +10904,12 @@ scoped-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/scoped-regex/-/scoped-regex-1.0.0.tgz#a346bb1acd4207ae70bd7c0c7ca9e566b6baddb8" +scroll@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/scroll/-/scroll-2.0.3.tgz#0951b785544205fd17753bc3d294738ba16fc2ab" + dependencies: + rafl "~1.2.1" + scss-tokenizer@^0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz#8eb06db9a9723333824d3f5530641149847ce5d1" From ebe23894f48bc9086b8c39bb5e3bf6e14ce71752 Mon Sep 17 00:00:00 2001 From: epiqueras Date: Thu, 26 Apr 2018 22:30:17 -0700 Subject: [PATCH 2/5] fix(joyride): fix set up --- src/bootstrap/app.scss | 2 +- .../iico/components/joyride/index.js | 9 +-- src/containers/iico/index.js | 79 +++++++++---------- 3 files changed, 42 insertions(+), 48 deletions(-) diff --git a/src/bootstrap/app.scss b/src/bootstrap/app.scss index 1dce8e7..f51a35e 100755 --- a/src/bootstrap/app.scss +++ b/src/bootstrap/app.scss @@ -1,7 +1,7 @@ @import '~normalize.css'; @import '~react-redux-toastr/lib/css/react-redux-toastr.min.css'; @import '~create-redux-form/animations/carousel.css'; -@import '~react-joyride/lib/react-joyride'; +@import '~react-joyride/lib/react-joyride-compiled.css'; @import '../styles/_colors.scss'; @import '../styles/_typography.scss'; @import '../styles/_toastr.scss'; diff --git a/src/containers/iico/components/joyride/index.js b/src/containers/iico/components/joyride/index.js index 41ec080..00f3667 100644 --- a/src/containers/iico/components/joyride/index.js +++ b/src/containers/iico/components/joyride/index.js @@ -4,21 +4,16 @@ import ReactJoyride from 'react-joyride' import steps from './steps' -const Joyride = ({ children, getRef }) => ( +const Joyride = ({ getRef }) => ( - {children} - + /> ) Joyride.propTypes = { - // State - children: PropTypes.node.isRequired, - // Callbacks getRef: PropTypes.func.isRequired } diff --git a/src/containers/iico/index.js b/src/containers/iico/index.js index d17667d..771c4ec 100644 --- a/src/containers/iico/index.js +++ b/src/containers/iico/index.js @@ -58,47 +58,46 @@ class IICO extends PureComponent { return (
- - - } - done={ - IICOData.data && ( -
-
- -
-
- } - done={ - IICOBids.data && ( - - ) - } - failedLoading="There was an error fetching your bids." - /> -
+ + + } + done={ + IICOData.data && ( +
+
+
- ) - } - failedLoading="The address or the contract it holds is invalid. Try another one." - /> - +
+ } + done={ + IICOBids.data && ( + + ) + } + failedLoading="There was an error fetching your bids." + /> +
+
+ ) + } + failedLoading="The address or the contract it holds is invalid. Try another one." + />
) } From e425bc9968a149404e94eee07a2a24061b7b0bd9 Mon Sep 17 00:00:00 2001 From: epiqueras Date: Mon, 30 Apr 2018 14:21:34 -0500 Subject: [PATCH 3/5] feat(joyride): finish first half --- src/bootstrap/app.scss | 4 +- .../__snapshots__/storyshots.test.js.snap | 40 +++++++++ src/components/nav-bar/nav-bar.scss | 3 +- src/components/stat-block/index.js | 5 +- src/components/stat-block/stat-block.scss | 10 +++ src/components/stat-row/index.js | 7 +- src/components/stat-row/stat-row.scss | 10 +++ src/containers/iico/components/data/index.js | 15 +++- .../iico/components/joyride/index.js | 11 +++ .../iico/components/joyride/steps.js | 85 ++++++++++++++++++- src/containers/iico/index.js | 14 +++ src/styles/_joyride.scss | 3 + 12 files changed, 199 insertions(+), 8 deletions(-) create mode 100644 src/styles/_joyride.scss diff --git a/src/bootstrap/app.scss b/src/bootstrap/app.scss index f51a35e..f2fee55 100755 --- a/src/bootstrap/app.scss +++ b/src/bootstrap/app.scss @@ -6,6 +6,7 @@ @import '../styles/_typography.scss'; @import '../styles/_toastr.scss'; @import '../styles/_form.scss'; +@import '../styles/_joyride.scss'; // Global Overrides * { @@ -18,6 +19,7 @@ html { color: $dark2; font-size: 16px; + overflow: scroll; } // App Containers @@ -47,7 +49,7 @@ body, background-size: cover; display: flex; flex-direction: column; - overflow: scroll; + // overflow: scroll; position: relative; } diff --git a/src/components/__snapshots__/storyshots.test.js.snap b/src/components/__snapshots__/storyshots.test.js.snap index 13a014d..b6e6882 100755 --- a/src/components/__snapshots__/storyshots.test.js.snap +++ b/src/components/__snapshots__/storyshots.test.js.snap @@ -5196,6 +5196,10 @@ exports[`Storyshots Stat Block default 1`] = ` > 300 +
300 +
300 +
300 +
300 +
+
300 +
300 +
300 +
+
( +const StatBlock = ({ id, label, value, noBackground, noFlex, flexBasis }) => (
( > {value} +
) StatBlock.propTypes = { // State + id: PropTypes.string, label: PropTypes.string, value: PropTypes.node.isRequired, @@ -34,6 +36,7 @@ StatBlock.propTypes = { StatBlock.defaultProps = { // State + id: undefined, label: null, // Modifiers diff --git a/src/components/stat-block/stat-block.scss b/src/components/stat-block/stat-block.scss index 027a902..4768e75 100644 --- a/src/components/stat-block/stat-block.scss +++ b/src/components/stat-block/stat-block.scss @@ -5,6 +5,7 @@ flex-direction: column; justify-content: flex-end; margin: 10px; + position: relative; &--withNoLabel { border-radius: 5px; @@ -20,4 +21,13 @@ margin: 0; } } + + &-joyrideTarget { + height: 100%; + left: 0; + pointer-events: none; + position: absolute; + top: -75px; + width: 100%; + } } diff --git a/src/components/stat-row/index.js b/src/components/stat-row/index.js index 329b1d4..336b6a8 100644 --- a/src/components/stat-row/index.js +++ b/src/components/stat-row/index.js @@ -3,14 +3,16 @@ import PropTypes from 'prop-types' import './stat-row.css' -const StatRow = ({ children, withBoxShadow }) => ( +const StatRow = ({ id, children, withBoxShadow }) => (
{children}
+
) StatRow.propTypes = { // State + id: PropTypes.string, children: PropTypes.node.isRequired, // Modifiers @@ -18,6 +20,9 @@ StatRow.propTypes = { } StatRow.defaultProps = { + // State + id: undefined, + // Modifiers withBoxShadow: false } diff --git a/src/components/stat-row/stat-row.scss b/src/components/stat-row/stat-row.scss index b5e0d53..353096b 100644 --- a/src/components/stat-row/stat-row.scss +++ b/src/components/stat-row/stat-row.scss @@ -4,6 +4,7 @@ .StatRow { border-bottom: 2px solid $light3; padding: 25.5px 0 0 0; + position: relative; width: 100%; &--withBoxShadow { @@ -24,4 +25,13 @@ justify-content: center; margin: -10px; } + + &-joyrideTarget { + height: 100%; + left: 0; + pointer-events: none; + position: absolute; + top: -75px; + width: 100%; + } } diff --git a/src/containers/iico/components/data/index.js b/src/containers/iico/components/data/index.js index 2b9b78d..711e458 100644 --- a/src/containers/iico/components/data/index.js +++ b/src/containers/iico/components/data/index.js @@ -64,31 +64,40 @@ export default class Data extends PureComponent { return (
- + {data.tokenContractAddress}} /> - + {data.valuation || 0}} /> - +
( ref={getRef} steps={steps} run + autoStart + locale={{ + back: 'Go Back', + close: 'Close', + last: 'Finish', + next: 'Next', + skip: 'Skip' + }} + showSkipButton + showStepsProgress + type="continuous" debug={process.env.NODE_ENV !== 'production'} /> ) diff --git a/src/containers/iico/components/joyride/steps.js b/src/containers/iico/components/joyride/steps.js index 9859f07..cba0ddf 100644 --- a/src/containers/iico/components/joyride/steps.js +++ b/src/containers/iico/components/joyride/steps.js @@ -1 +1,84 @@ -export default [] +import React from 'react' + +export default [ + { + title: 'Welcome to the Interactive Coin Offering.', + text: ( + + Get started with the tutorial or skip it if you already know what you + are doing. +
+
+ First, let’s go over the sale’s main data points and explain what they + are. A full explanation can be found{' '} + + here + . +
+ ), + selector: '#joyrideWelcome' + }, + { + text: + 'This is the ICO’s contract address. You can hover over it to see it in full and you can click on it to explore the contract on etherscan. Try doing that now.', + selector: '#joyrideTokenContractAddress' + }, + { + text: + 'This is the amount of tokens that are up for sale. They will be distributed proportionally across all bidders relative to the size of their contributions and bonuses.', + selector: '#joyrideTokensForSale' + }, + { + text: + 'This is the sale’s current valuation. That is, if the sale were to end now and personal caps were taken into account to see which ones should be refunded. The number is truncated to two significant digits, but you can hover over it to see it in full.', + selector: '#joyrideValuation' + }, + { + text: ( + + This is the sale’s current phase. There are four phases and the actions + you can take are different in each one. +
+
+ Not Started: The sale has not started yet at this point, so there + is nothing you can do. +
+
+ Full Bonus: You may place bids that take advantage of the whole + bonus and withdrawing a bid will not leave any ETH locked in. +
+
+ Partial Withdrawals: You may still place bids, but the bonus has + started decreasing linearly, and the amount of ETH locked in when + withdrawing has started increasing linearly. +
+
+ Withdrawal Lockup: You may still place bids, but manual + withdrawals are no longer permitted. The only way for a bid to be + refunded is if the valuation exceeds its personal cap. +
+
+ Finished: You may now redeem your tokens and/or ETH. +
+ ), + selector: '#joyridePhase' + }, + { + text: + 'This is the sale’s starting bonus. This will start decreasing linearly at the end of the full bonus phase down to zero at the end of the sale. A bid’s bonus increases its token purchasing power. For example, a 20% bonus will give you 20% more tokens for the same amount of ETH.', + selector: '#joyrideStartingBonus' + }, + { + text: 'This is the sale’s current bonus.', + selector: '#joyrideCurrentBonus' + }, + { + text: + 'This slider lets you preview the bonus throughout the lifetime of the sale. The green bars represent a change of phase and the light blue bar represents the current time. Hover over it now to preview the bonus at any stage of the sale. Let’s skip time to the start of the sale.', + selector: '#joyrideSlider' + } +] diff --git a/src/containers/iico/index.js b/src/containers/iico/index.js index 771c4ec..c3a4c09 100644 --- a/src/containers/iico/index.js +++ b/src/containers/iico/index.js @@ -31,6 +31,10 @@ class IICO extends PureComponent { fetchIICOBids: PropTypes.func.isRequired } + state = { + hasSeenTutorial: false + } + pollIICODataInterval = null joyrideRef = null @@ -47,6 +51,16 @@ class IICO extends PureComponent { this.pollIICODataInterval = setInterval(() => pollIICOData(address), 5000) } + componentDidUpdate() { + const { IICOData, IICOBids } = this.props + const { hasSeenTutorial } = this.state + + if (IICOData.data && IICOBids.data && !hasSeenTutorial) + this.setState({ hasSeenTutorial: true }, () => + this.joyrideRef.reset(true) + ) + } + componentWillUnmount() { clearInterval(this.pollIICODataInterval) } diff --git a/src/styles/_joyride.scss b/src/styles/_joyride.scss new file mode 100644 index 0000000..923fed9 --- /dev/null +++ b/src/styles/_joyride.scss @@ -0,0 +1,3 @@ +.joyride-overlay { + pointer-events: none !important; +} From 09931e64047e8eeaa8f65b17ddafc971e2b3d4c9 Mon Sep 17 00:00:00 2001 From: epiqueras Date: Mon, 30 Apr 2018 16:28:58 -0500 Subject: [PATCH 4/5] feat(joyride): implement conditional steps --- src/bootstrap/app.js | 12 ++- src/bootstrap/app.scss | 21 ++--- src/components/nav-bar/nav-bar.scss | 1 - src/components/stat-block/stat-block.scss | 2 +- src/components/stat-row/stat-row.scss | 2 +- src/containers/iico/components/bids/index.js | 18 +++-- src/containers/iico/components/data/index.js | 12 ++- .../iico/components/joyride/index.js | 10 ++- .../iico/components/joyride/steps.js | 22 +++++ src/containers/iico/index.js | 80 +++++++++++++++++-- 10 files changed, 136 insertions(+), 44 deletions(-) diff --git a/src/bootstrap/app.js b/src/bootstrap/app.js index 0ab702a..d04ed56 100755 --- a/src/bootstrap/app.js +++ b/src/bootstrap/app.js @@ -35,13 +35,11 @@ const App = ({ store, history, testElement }) => ( Open IICO -
- - - - - -
+ + + + + {testElement}
diff --git a/src/bootstrap/app.scss b/src/bootstrap/app.scss index f2fee55..6f50fe2 100755 --- a/src/bootstrap/app.scss +++ b/src/bootstrap/app.scss @@ -17,6 +17,11 @@ // Document Root html { + background-color: $light2; + background-image: url('../assets/images/background.png'); + background-position: -90px 180px; + background-repeat: no-repeat; + background-size: cover; color: $dark2; font-size: 16px; overflow: scroll; @@ -26,10 +31,8 @@ html { html, body, #root, -#router-root, -#scroll-root { +#router-root { border: 0; - height: 100%; margin: 0; padding: 0; width: 100%; @@ -37,19 +40,9 @@ body, // React Router Root #router-root { - padding-top: 70px; -} - -// Scroll Root -#scroll-root { - background-color: $light2; - background-image: url('../assets/images/background.png'); - background-position: -90px 180px; - background-repeat: no-repeat; - background-size: cover; display: flex; flex-direction: column; - // overflow: scroll; + padding-top: 70px; position: relative; } diff --git a/src/components/nav-bar/nav-bar.scss b/src/components/nav-bar/nav-bar.scss index b5c4215..2c4406b 100644 --- a/src/components/nav-bar/nav-bar.scss +++ b/src/components/nav-bar/nav-bar.scss @@ -27,7 +27,6 @@ display: flex; height: 100%; margin: 0 50px 0 25px; - // overflow-x: scroll; text-overflow: ellipsis; white-space: nowrap; diff --git a/src/components/stat-block/stat-block.scss b/src/components/stat-block/stat-block.scss index 4768e75..41bb34a 100644 --- a/src/components/stat-block/stat-block.scss +++ b/src/components/stat-block/stat-block.scss @@ -27,7 +27,7 @@ left: 0; pointer-events: none; position: absolute; - top: -75px; + top: -10px; width: 100%; } } diff --git a/src/components/stat-row/stat-row.scss b/src/components/stat-row/stat-row.scss index 353096b..9710ab1 100644 --- a/src/components/stat-row/stat-row.scss +++ b/src/components/stat-row/stat-row.scss @@ -31,7 +31,7 @@ left: 0; pointer-events: none; position: absolute; - top: -75px; + top: 10px; width: 100%; } } diff --git a/src/containers/iico/components/bids/index.js b/src/containers/iico/components/bids/index.js index 8e52eb0..5a544a8 100644 --- a/src/containers/iico/components/bids/index.js +++ b/src/containers/iico/components/bids/index.js @@ -51,7 +51,12 @@ class Bids extends PureComponent { updatingBids: PropTypes.oneOfType([ PropTypes.bool, PropTypes.arrayOf(PropTypes.number.isRequired) - ]).isRequired + ]).isRequired, + tutorialNow: PropTypes.number + } + + static defaultProps = { + tutorialNow: null } handleSubmitBidFormSubmit = formData => { @@ -65,9 +70,9 @@ class Bids extends PureComponent { } handleWithdrawClick = ({ currentTarget: { id: _id } }) => { - const { address, data, bids, withdrawIICOBid } = this.props + const { withdrawIICOBid, address, data, bids, tutorialNow } = this.props const id = Number(_id) - const now = Date.now() + const now = tutorialNow || Date.now() const endFullBonusTime = data.endFullBonusTime.getTime() const withdrawalLockTime = data.withdrawalLockTime.getTime() const bid = bids.find(b => b.ID === id) @@ -122,10 +127,11 @@ class Bids extends PureComponent { submitFinalizeIICOForm, data, bids, - updatingBids + updatingBids, + tutorialNow } = this.props - const now = Date.now() + const now = tutorialNow || Date.now() const hasStarted = now >= data.startTime.getTime() const hasEnded = now >= data.endTime.getTime() const canBid = hasStarted && !hasEnded @@ -133,7 +139,7 @@ class Bids extends PureComponent { const canRedeem = hasEnded && data.finalized return ( -
+

Your Bids

{canBid && ( diff --git a/src/containers/iico/components/data/index.js b/src/containers/iico/components/data/index.js index 711e458..27ebef9 100644 --- a/src/containers/iico/components/data/index.js +++ b/src/containers/iico/components/data/index.js @@ -1,4 +1,5 @@ import React, { PureComponent } from 'react' +import PropTypes from 'prop-types' import * as IICOSelectors from '../../../../reducers/iico' import StatRow from '../../../../components/stat-row' @@ -14,7 +15,12 @@ import './data.css' export default class Data extends PureComponent { static propTypes = { // State - data: IICOSelectors._IICODataShape.isRequired + data: IICOSelectors._IICODataShape.isRequired, + tutorialNow: PropTypes.number + } + + static defaultProps = { + tutorialNow: null } calcBonus = percent => { @@ -35,10 +41,10 @@ export default class Data extends PureComponent { } render() { - const { data } = this.props + const { data, tutorialNow } = this.props // Times - const now = Date.now() + const now = tutorialNow || Date.now() const startTime = data.startTime.getTime() const endTime = data.endTime.getTime() const endFullBonusTime = data.endFullBonusTime.getTime() diff --git a/src/containers/iico/components/joyride/index.js b/src/containers/iico/components/joyride/index.js index 9fe1c8e..fb4fb8c 100644 --- a/src/containers/iico/components/joyride/index.js +++ b/src/containers/iico/components/joyride/index.js @@ -4,29 +4,31 @@ import ReactJoyride from 'react-joyride' import steps from './steps' -const Joyride = ({ getRef }) => ( +const Joyride = ({ getRef, callback }) => ( ) Joyride.propTypes = { // Callbacks - getRef: PropTypes.func.isRequired + getRef: PropTypes.func.isRequired, + callback: PropTypes.func.isRequired } export default Joyride diff --git a/src/containers/iico/components/joyride/steps.js b/src/containers/iico/components/joyride/steps.js index cba0ddf..b8ad255 100644 --- a/src/containers/iico/components/joyride/steps.js +++ b/src/containers/iico/components/joyride/steps.js @@ -80,5 +80,27 @@ export default [ text: 'This slider lets you preview the bonus throughout the lifetime of the sale. The green bars represent a change of phase and the light blue bar represents the current time. Hover over it now to preview the bonus at any stage of the sale. Let’s skip time to the start of the sale.', selector: '#joyrideSlider' + }, + { + text: ( + + This is where you can make bids. The personal cap lets you set a max cap + at which you are willing to participate in the sale with. If the + valuation ends up exceeding this value, your bid will be automatically + refunded. +
+
+ If you want to participate regardless of valuation, just check the “No + Personal Cap” checkbox. +
+
+ Try placing a bid now and take advantage of the “Full Bonus” phase. +
+
+ Place a bid to continue. +
+ ), + selector: '#joyridePlaceBid', + style: { button: { display: 'none' } } } ] diff --git a/src/containers/iico/index.js b/src/containers/iico/index.js index c3a4c09..8888b1f 100644 --- a/src/containers/iico/index.js +++ b/src/containers/iico/index.js @@ -32,7 +32,11 @@ class IICO extends PureComponent { } state = { - hasSeenTutorial: false + hasSeenTutorial: false, + inTutorial: false, + tutorialNow: null, + tutorialIICOData: null, + tutorialIICOBids: null } pollIICODataInterval = null @@ -55,10 +59,34 @@ class IICO extends PureComponent { const { IICOData, IICOBids } = this.props const { hasSeenTutorial } = this.state - if (IICOData.data && IICOBids.data && !hasSeenTutorial) - this.setState({ hasSeenTutorial: true }, () => - this.joyrideRef.reset(true) + if (IICOData.data && IICOBids.data && !hasSeenTutorial) { + const tutorialIICOData = JSON.parse(JSON.stringify(IICOData)) + const tutorialIICOBids = JSON.parse(JSON.stringify(IICOBids)) + const startTime = IICOData.data.startTime.getTime() + this.setState( + { + hasSeenTutorial: true, + inTutorial: true, + tutorialNow: startTime - 1000, + tutorialIICOData: { + ...tutorialIICOData, + data: { + ...tutorialIICOData.data, + startTime: new Date(startTime), + endFullBonusTime: new Date( + IICOData.data.endFullBonusTime.getTime() + ), + withdrawalLockTime: new Date( + IICOData.data.withdrawalLockTime.getTime() + ), + endTime: new Date(IICOData.data.endTime.getTime()) + } + }, + tutorialIICOBids + }, + () => this.joyrideRef.reset(true) ) + } } componentWillUnmount() { @@ -67,12 +95,49 @@ class IICO extends PureComponent { getJoyrideRef = ref => (this.joyrideRef = ref) + joyrideCallback = ({ type, step }) => { + const { tutorialIICOData } = this.state + + switch (type) { + case 'step:after': + switch (step.selector.slice('#joyride'.length)) { + case 'Slider': + this.setState({ + tutorialNow: tutorialIICOData.data.startTime.getTime() + }) + break + default: + break + } + break + case 'finished': + this.setState({ + inTutorial: false, + tutorialNow: null, + tutorialIICOData: null, + tutorialIICOBids: null + }) + break + default: + break + } + } + render() { - const { match: { params: { address } }, IICOData, IICOBids } = this.props + const { match } = this.props + const { + inTutorial, + tutorialNow, + tutorialIICOData, + tutorialIICOBids + } = this.state + const { match: { params: { address } }, IICOData, IICOBids } = inTutorial + ? { match, IICOData: tutorialIICOData, IICOBids: tutorialIICOBids } + : this.props return (
- +
- +
) } From 9594edabc45324636f99fbdc1d5bb0b513ce1552 Mon Sep 17 00:00:00 2001 From: epiqueras Date: Tue, 1 May 2018 17:13:07 -0500 Subject: [PATCH 5/5] feat(joyride): finish all steps --- .../requires-meta-mask/require-meta-mask.scss | 7 +- src/containers/iico/components/bids/index.js | 146 +++++++++++++----- src/containers/iico/components/data/index.js | 2 +- .../iico/components/joyride/index.js | 1 + .../iico/components/joyride/steps.js | 68 ++++++++ src/containers/iico/index.js | 116 +++++++++++++- src/styles/_joyride.scss | 18 ++- 7 files changed, 314 insertions(+), 44 deletions(-) diff --git a/src/components/requires-meta-mask/require-meta-mask.scss b/src/components/requires-meta-mask/require-meta-mask.scss index ad03b15..55d2b36 100755 --- a/src/components/requires-meta-mask/require-meta-mask.scss +++ b/src/components/requires-meta-mask/require-meta-mask.scss @@ -1,16 +1,17 @@ +@import '../../styles/_colors.scss'; + /* @define RequiresMetaMask */ .RequiresMetaMask { - background: #273142; height: 100%; &-message { - color: white; + color: $dark; font-size: 40px; padding: 70px 50px; text-align: center; &-link { - color: #999; + color: $dark4; text-decoration: none; } } diff --git a/src/containers/iico/components/bids/index.js b/src/containers/iico/components/bids/index.js index 5a544a8..3f36a10 100644 --- a/src/containers/iico/components/bids/index.js +++ b/src/containers/iico/components/bids/index.js @@ -6,6 +6,7 @@ import { SyncLoader } from 'react-spinners' import * as IICOSelectors from '../../../../reducers/iico' import * as IICOActions from '../../../../actions/iico' +import * as walletSelectors from '../../../../reducers/wallet' import { SubmitBidForm, getSubmitBidFormIsInvalid, @@ -28,6 +29,7 @@ class Bids extends PureComponent { static propTypes = { // Redux State IICOBid: IICOSelectors.IICOBidShape.isRequired, + accounts: walletSelectors.accountsShape.isRequired, // Action Dispatchers createIICOBid: PropTypes.func.isRequired, @@ -52,7 +54,10 @@ class Bids extends PureComponent { PropTypes.bool, PropTypes.arrayOf(PropTypes.number.isRequired) ]).isRequired, - tutorialNow: PropTypes.number + tutorialNow: PropTypes.number, + tutorialFinalizeIICOData: PropTypes.func.isRequired, + tutorialEditIICOBids: PropTypes.func.isRequired, + tutorialNext: PropTypes.func.isRequired } static defaultProps = { @@ -60,7 +65,32 @@ class Bids extends PureComponent { } handleSubmitBidFormSubmit = formData => { - const { address, createIICOBid } = this.props + const { + accounts, + address, + data, + bids, + createIICOBid, + tutorialNow, + tutorialEditIICOBids, + tutorialNext + } = this.props + if (tutorialNow) + return tutorialEditIICOBids( + { + ID: bids.length ? bids[bids.length - 1].ID + 1 : 0, + maxVal: Number(formData.personalCap), + contrib: Number(formData.amount), + bonus: data.bonus, + contributor: accounts.data[0], + withdrawn: false, + redeemed: false + }, + () => { + if (bids.length === 0) tutorialNext() + } + ) + createIICOBid( address, formData.amount, @@ -70,7 +100,15 @@ class Bids extends PureComponent { } handleWithdrawClick = ({ currentTarget: { id: _id } }) => { - const { withdrawIICOBid, address, data, bids, tutorialNow } = this.props + const { + withdrawIICOBid, + address, + data, + bids, + tutorialNow, + tutorialEditIICOBids, + tutorialNext + } = this.props const id = Number(_id) const now = tutorialNow || Date.now() const endFullBonusTime = data.endFullBonusTime.getTime() @@ -88,7 +126,10 @@ class Bids extends PureComponent { toastr.confirm(null, { okText: 'Yes', - onOk: () => withdrawIICOBid(address, id), + onOk: () => + tutorialNow + ? tutorialEditIICOBids(id, () => tutorialNext(), lockedIn, newBonus) + : withdrawIICOBid(address, id), component: () => (
Are you sure you wish to withdraw this bid? @@ -104,17 +145,35 @@ class Bids extends PureComponent { } handleFinalizeIICOFormSubmit = formData => { - const { address, finalizeIICO } = this.props + const { + address, + finalizeIICO, + tutorialNow, + tutorialFinalizeIICOData, + tutorialNext + } = this.props + if (tutorialNow) return tutorialFinalizeIICOData(tutorialNext) + finalizeIICO(address, formData.maxIterations) } - handleRedeemClick = ({ currentTarget: { id } }) => { - const { address, redeemIICOBid } = this.props + handleRedeemClick = ({ currentTarget: { id: _id } }) => { + const { + address, + redeemIICOBid, + tutorialNow, + tutorialEditIICOBids, + tutorialNext + } = this.props + const id = Number(_id) + if (tutorialNow) return tutorialEditIICOBids(id, tutorialNext) + redeemIICOBid(address, Number(id)) } handleRedeemAllClick = () => { const { address, redeemIICOBids } = this.props + redeemIICOBids(address) } @@ -134,42 +193,51 @@ class Bids extends PureComponent { const now = tutorialNow || Date.now() const hasStarted = now >= data.startTime.getTime() const hasEnded = now >= data.endTime.getTime() - const canBid = hasStarted && !hasEnded + const canWithdraw = hasStarted && now < data.withdrawalLockTime.getTime() const canRedeem = hasEnded && data.finalized + const inPartialWithdrawals = now > data.endFullBonusTime.getTime() return (

Your Bids

- {canBid && ( - - - } - /> - - ADD - - } - noFlex - /> - - )} + {hasStarted && + !hasEnded && ( + + + } + /> + 0) + } + > + ADD + + } + noFlex + /> + + )} {canRedeem && bids.some(b => !b.redeemed) && ( + } @@ -234,8 +302,8 @@ class Bids extends PureComponent { let refund = b.contrib if (b.ID === data.cutOffBidID) { // This is the cutoff bid - contrib = data.cutOffContrib - refund = b.contrib - data.cutOffContrib + contrib = data.cutOffBidContrib + refund = b.contrib - data.cutOffBidContrib } else if ( b.maxVal > data.cutOffBidMaxVal || (b.maxVal === data.cutOffBidMaxVal && b.ID > data.cutOffBidID) @@ -248,7 +316,7 @@ class Bids extends PureComponent { const updating = updatingBids && updatingBids.includes(b.ID) return ( - + {contrib}} @@ -275,12 +343,14 @@ class Bids extends PureComponent { } /> {refund}} /> {((canWithdraw && !b.withdrawn) || (canRedeem && !b.redeemed)) && ( {updating ? ( @@ -320,6 +393,7 @@ class Bids extends PureComponent { export default connect( state => ({ IICOBid: state.IICO.IICOBid, + accounts: state.wallet.accounts, submitBidFormIsInvalid: getSubmitBidFormIsInvalid(state), finalizeIICOFormIsInvalid: getFinalizeIICOFormIsInvalid(state) }), diff --git a/src/containers/iico/components/data/index.js b/src/containers/iico/components/data/index.js index 27ebef9..a657c7e 100644 --- a/src/containers/iico/components/data/index.js +++ b/src/containers/iico/components/data/index.js @@ -46,9 +46,9 @@ export default class Data extends PureComponent { // Times const now = tutorialNow || Date.now() const startTime = data.startTime.getTime() - const endTime = data.endTime.getTime() const endFullBonusTime = data.endFullBonusTime.getTime() const withdrawalLockTime = data.withdrawalLockTime.getTime() + const endTime = data.endTime.getTime() const duration = endTime - startTime // Phase diff --git a/src/containers/iico/components/joyride/index.js b/src/containers/iico/components/joyride/index.js index fb4fb8c..f31641d 100644 --- a/src/containers/iico/components/joyride/index.js +++ b/src/containers/iico/components/joyride/index.js @@ -10,6 +10,7 @@ const Joyride = ({ getRef, callback }) => ( steps={steps} run autoStart + keyboardNavigation={false} locale={{ close: 'Close', last: 'Finish', diff --git a/src/containers/iico/components/joyride/steps.js b/src/containers/iico/components/joyride/steps.js index b8ad255..4987cc8 100644 --- a/src/containers/iico/components/joyride/steps.js +++ b/src/containers/iico/components/joyride/steps.js @@ -102,5 +102,73 @@ export default [ ), selector: '#joyridePlaceBid', style: { button: { display: 'none' } } + }, + { + text: + 'Congratulations on placing your first bid! Withdrawing now would give you all of your ETH back, but let’s skip through time to demonstrate the lock in process.', + selector: '#joyridePlacedBid' + }, + { + text: ( + + We are now in the “Partial Withdrawals” phase. +
+
+ Try withdrawing your bid, you’ll only be able to withdraw whatever + percentage of the “Partial Withdrawals” phase is left and your bonus + will be reduced by 1/3. +
+
+ That is, if you are 80% through the phase, you’ll only be able to + withdraw 20% of your bid and if your bonus was 15% it will be reduced to + 10%. This is to avoid blackout attacks by large players. +
+
+ See this article to learn more. +
+ ), + selector: '#joyrideWithdraw', + style: { button: { display: 'none' } } + }, + { + text: "Nice, you've been refunded. Now, let’s skip to the end of the sale.", + selector: '#joyrideWithdrew' + }, + { + text: ( + + Now, the contract needs to iterate over all the bids to finalize the + sale. This may be done by anyone so you might not even get to see this + part of the sale. +
+
+ Enter the number of iterations you’d like to pay gas for and submit to + finalize the sale. +
+
+ Five iterations is more than enough since you only made one bid. +
+ ), + selector: '#joyridePlaceBid', + style: { button: { display: 'none' } } + }, + { + text: ( + + Good job! Now you can redeem your tokens (for bids that stayed in the + sale) and/or ETH (for bids where the personal cap ended up being under + the valuation). +
+
+ Redeem your bid to continue. +
+ ), + selector: '#joyrideWithdraw', + style: { button: { display: 'none' } } + }, + { + text: + 'Great, you are now more than ready to take part in a real Interactive Coin Offering. Please let us know if you have any questions or concerns, feedback is greatly appreciated.', + selector: '#joyrideFinish' } ] diff --git a/src/containers/iico/index.js b/src/containers/iico/index.js index 8888b1f..f2d1378 100644 --- a/src/containers/iico/index.js +++ b/src/containers/iico/index.js @@ -79,7 +79,8 @@ class IICO extends PureComponent { withdrawalLockTime: new Date( IICOData.data.withdrawalLockTime.getTime() ), - endTime: new Date(IICOData.data.endTime.getTime()) + endTime: new Date(IICOData.data.endTime.getTime()), + bonus: IICOData.data.startingBonus } }, tutorialIICOBids @@ -106,6 +107,28 @@ class IICO extends PureComponent { tutorialNow: tutorialIICOData.data.startTime.getTime() }) break + case 'PlacedBid': { + const endFullBonusTime = tutorialIICOData.data.endFullBonusTime.getTime() + const endTime = tutorialIICOData.data.endTime.getTime() + const tutorialNow = + endFullBonusTime + + (tutorialIICOData.data.withdrawalLockTime.getTime() - + endFullBonusTime) / + 2 + this.setState({ + tutorialNow, + bonus: + tutorialIICOData.data.bonus * + ((endTime - tutorialNow) / (endTime - endFullBonusTime)) + }) + break + } + case 'Withdrew': + this.setState({ + tutorialNow: tutorialIICOData.data.endTime.getTime(), + bonus: 0 + }) + break default: break } @@ -123,6 +146,90 @@ class IICO extends PureComponent { } } + tutorialFinalizeIICOData = callback => { + const { tutorialIICOData } = this.state + + this.setState( + { + tutorialIICOData: { + ...tutorialIICOData, + data: { + ...tutorialIICOData.data, + finalized: true + } + } + }, + callback + ) + } + + tutorialEditIICOBids = (IICOBidOrID, callback, lockedIn, newBonus) => { + const { tutorialIICOData, tutorialIICOBids } = this.state + + let newBids + if (typeof IICOBidOrID === 'number') + newBids = tutorialIICOBids.data.map( + b => + b.ID === IICOBidOrID + ? lockedIn + ? { ...b, contrib: lockedIn, bonus: newBonus, withdrawn: true } + : { ...b, redeemed: true } + : b + ) + else newBids = [...tutorialIICOBids.data, IICOBidOrID] + console.log(newBids) + // Calculate new tutorial IICO Data + const bids = [...newBids].sort((a, b) => { + if (b.maxVal === a.maxVal) return b.ID - a.ID + return b.maxVal - a.maxVal + }) + let cutOffBidContrib + let cutOffBid = bids[bids.length - 1] + let valuation = 0 + let virtualValuation = 0 + for (const bid of bids) { + if (bid.contrib + valuation < bid.maxVal) { + // We haven't found the cut-off yet. + cutOffBidContrib = bid.contrib + cutOffBid = bid + valuation += bid.contrib + virtualValuation += bid.contrib + bid.contrib * (1 + bid.bonus) + } else { + // We found the cut-off bid. This bid will be taken partially. + cutOffBidContrib = bid.maxVal >= valuation ? bid.maxVal - valuation : 0 // The amount of the contribution of the cut-off bid that can stay in the sale without spilling over the maxVal. + cutOffBid = bid + valuation += cutOffBidContrib + virtualValuation += + cutOffBidContrib + cutOffBidContrib * (1 + bid.bonus) + break + } + } + + // Set new tutorial state + this.setState( + { + tutorialIICOData: { + ...tutorialIICOData, + data: { + ...tutorialIICOData.data, + valuation, + virtualValuation, + cutOffBidID: cutOffBid.ID, + cutOffBidMaxVal: cutOffBid.maxVal, + cutOffBidContrib + } + }, + tutorialIICOBids: { + ...tutorialIICOBids, + data: newBids + } + }, + callback + ) + } + + tutorialNext = () => this.joyrideRef.next() + render() { const { match } = this.props const { @@ -152,7 +259,7 @@ class IICO extends PureComponent { done={ IICOData.data && (
-
+
@@ -167,6 +274,11 @@ class IICO extends PureComponent { bids={IICOBids.data} updatingBids={IICOBids.updating} tutorialNow={tutorialNow} + tutorialFinalizeIICOData={ + this.tutorialFinalizeIICOData + } + tutorialEditIICOBids={this.tutorialEditIICOBids} + tutorialNext={this.tutorialNext} /> ) } diff --git a/src/styles/_joyride.scss b/src/styles/_joyride.scss index 923fed9..680d94c 100644 --- a/src/styles/_joyride.scss +++ b/src/styles/_joyride.scss @@ -1,3 +1,17 @@ -.joyride-overlay { - pointer-events: none !important; +@import '../styles/_colors.scss'; + +.joyride { + &-overlay { + pointer-events: none !important; + } + + &-tooltip { + &__header { + border-color: $primary !important; + } + + &__button--primary { + background: $primary !important; + } + } }