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.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 f50cbc3..6f50fe2 100755 --- a/src/bootstrap/app.scss +++ b/src/bootstrap/app.scss @@ -1,10 +1,12 @@ @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-compiled.css'; @import '../styles/_colors.scss'; @import '../styles/_typography.scss'; @import '../styles/_toastr.scss'; @import '../styles/_form.scss'; +@import '../styles/_joyride.scss'; // Global Overrides * { @@ -15,18 +17,22 @@ // 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; } // App Containers html, body, #root, -#router-root, -#scroll-root { +#router-root { border: 0; - height: 100%; margin: 0; padding: 0; width: 100%; @@ -34,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/__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 +
+
( +export default () => (
404. This is not the page you are looking for.
) - -export default PageNotFound 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/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/components/stat-block/index.js b/src/components/stat-block/index.js index 142f00e..5271e64 100644 --- a/src/components/stat-block/index.js +++ b/src/components/stat-block/index.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types' import './stat-block.css' -const StatBlock = ({ label, value, noBackground, noFlex, flexBasis }) => ( +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..41bb34a 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: -10px; + 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..9710ab1 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: 10px; + width: 100%; + } } diff --git a/src/containers/iico/components/bids/index.js b/src/containers/iico/components/bids/index.js index 8e52eb0..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, @@ -51,11 +53,44 @@ class Bids extends PureComponent { updatingBids: PropTypes.oneOfType([ PropTypes.bool, PropTypes.arrayOf(PropTypes.number.isRequired) - ]).isRequired + ]).isRequired, + tutorialNow: PropTypes.number, + tutorialFinalizeIICOData: PropTypes.func.isRequired, + tutorialEditIICOBids: PropTypes.func.isRequired, + tutorialNext: PropTypes.func.isRequired + } + + static defaultProps = { + tutorialNow: null } 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, @@ -65,9 +100,17 @@ class Bids extends PureComponent { } handleWithdrawClick = ({ currentTarget: { id: _id } }) => { - const { address, data, bids, withdrawIICOBid } = this.props + const { + withdrawIICOBid, + address, + data, + bids, + tutorialNow, + tutorialEditIICOBids, + tutorialNext + } = 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) @@ -83,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? @@ -99,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) } @@ -122,48 +186,58 @@ 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 + 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) && ( + } @@ -228,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) @@ -242,7 +316,7 @@ class Bids extends PureComponent { const updating = updatingBids && updatingBids.includes(b.ID) return ( - + {contrib}} @@ -269,12 +343,14 @@ class Bids extends PureComponent { } /> {refund}} /> {((canWithdraw && !b.withdrawn) || (canRedeem && !b.redeemed)) && ( {updating ? ( @@ -314,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 2b9b78d..a657c7e 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,14 +41,14 @@ 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() const withdrawalLockTime = data.withdrawalLockTime.getTime() + const endTime = data.endTime.getTime() const duration = endTime - startTime // Phase @@ -64,31 +70,40 @@ export default class Data extends PureComponent { return (
- + {data.tokenContractAddress}} /> - + {data.valuation || 0}} /> - +
( + +) + +Joyride.propTypes = { + // Callbacks + 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 new file mode 100644 index 0000000..4987cc8 --- /dev/null +++ b/src/containers/iico/components/joyride/steps.js @@ -0,0 +1,174 @@ +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' + }, + { + 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' } } + }, + { + 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 cc6b5e0..f2d1378 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,17 @@ class IICO extends PureComponent { fetchIICOBids: PropTypes.func.isRequired } + state = { + hasSeenTutorial: false, + inTutorial: false, + tutorialNow: null, + tutorialIICOData: null, + tutorialIICOBids: null + } + + pollIICODataInterval = null + joyrideRef = null + componentDidMount() { const { match: { params: { address } }, @@ -43,15 +55,196 @@ 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) { + 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()), + bonus: IICOData.data.startingBonus + } + }, + tutorialIICOBids + }, + () => this.joyrideRef.reset(true) + ) + } + } + componentWillUnmount() { clearInterval(this.pollIICODataInterval) } + 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 + 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 + } + break + case 'finished': + this.setState({ + inTutorial: false, + tutorialNow: null, + tutorialIICOData: null, + tutorialIICOBids: null + }) + break + default: + break + } + } + + 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: { 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 (
+ -
- +
+
) } @@ -91,8 +290,6 @@ class IICO extends PureComponent { } 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 */}
) } diff --git a/src/styles/_joyride.scss b/src/styles/_joyride.scss new file mode 100644 index 0000000..680d94c --- /dev/null +++ b/src/styles/_joyride.scss @@ -0,0 +1,17 @@ +@import '../styles/_colors.scss'; + +.joyride { + &-overlay { + pointer-events: none !important; + } + + &-tooltip { + &__header { + border-color: $primary !important; + } + + &__button--primary { + background: $primary !important; + } + } +} 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"