Skip to content

Instantly share code, notes, and snippets.

@PaulRBerg
Last active May 18, 2019 21:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save PaulRBerg/fbce504b2bed835501b69bcef57991ff to your computer and use it in GitHub Desktop.
Save PaulRBerg/fbce504b2bed835501b69bcef57991ff to your computer and use it in GitHub Desktop.
Parent component for StackOverflow question
import React, { Component } from "react";
import classnames from "classnames";
import PropTypes from "prop-types";
import ReactGA from "react-ga";
import ReactTooltip from "react-tooltip";
import { connect } from "react-redux";
import { push } from "connected-react-router";
import { Query } from "react-apollo";
import { withTranslation } from "react-i18next";
import CustomCircularProgressBar from "../../components/CustomCircularProgressBar";
import FaArrowCircleDown from "../../assets/images/fa-arrow-circle-down.svg";
import FaArrowCircleUp from "../../assets/images/fa-arrow-circle-up.svg";
import FaCalendarAlt from "../../assets/images/fa-calendar-alt.svg";
import FaCalendarCheck from "../../assets/images/fa-calendar-check.svg";
import FaEnvelope from "../../assets/images/fa-envelope.svg";
import FaHeartRate from "../../assets/images/fa-heart-rate.svg";
import FaInboxOut from "../../assets/images/fa-inbox-out.svg";
import FaInfoCircle from "../../assets/images/fa-info-circle.svg";
import FaPaste from "../../assets/images/fa-paste.svg";
import FaPlus from "../../assets/images/fa-plus.svg";
import FaStopwatch from "../../assets/images/fa-stopwatch.svg";
import FaTachometer from "../../assets/images/fa-tachometer.svg";
import FaTwitterBlack from "../../assets/images/fa-twitter-black.svg";
import links from "../../constants/links";
import PrimaryButton from "../../components/PrimaryButton";
import SablierABI from "../../abi/sablier";
import StopModal from "./StopModal";
import TokenLogo from "../../components/TokenLogo";
import WithdrawModal from "./WithdrawModal";
import { addPendingTx, selectors } from "../../redux/ducks/web3connect";
import { GET_STREAM } from "../../apollo/queries";
import { Parser } from "../../classes/parser";
import { StreamFlow } from "../../classes/stream";
import "react-circular-progressbar/dist/styles.css";
import "./stream.scss";
class Stream extends Component {
static propTypes = {
account: PropTypes.string,
addPendingTx: PropTypes.func.isRequired,
balances: PropTypes.object.isRequired,
blockNumber: PropTypes.number.isRequired,
push: PropTypes.func.isRequired,
sablierAddress: PropTypes.string,
selectors: PropTypes.func.isRequired,
web3: PropTypes.object.isRequired,
};
state = {
showStopModal: false,
showWithdrawModal: false,
};
componentDidMount() {
ReactGA.pageview(window.location.pathname + window.location.search);
}
goToDashboard() {
this.props.push("/dashboard");
}
goToNewStream() {
this.props.push("/");
}
onClickCopyLink() {
const { match } = this.props;
const link = `${links.share.sablier}${match.url}`;
navigator.clipboard.writeText(link);
}
onClickGoToDashboard() {
this.goToDashboard();
}
onClickInviteYourFriends() {
const { t } = this.props;
const subject = encodeURIComponent(t("mailto.subject"));
const body = [t("heya"), "👋", "\r\n\r\n", encodeURIComponent(t("mailto.body"))].join("");
const mailto = `mailto:?subject=${subject}&body=${body}`;
window.open(mailto);
}
onClickStopAndWithdraw() {
this.setState({ showStopModal: true });
}
onClickTweetAboutSablier() {
const { t } = this.props;
const intent = `https://twitter.com/intent/tweet?url=${links.share.sablier}&text=${t("shareTwitterText")}`;
window.open(intent, "_blank");
}
onSubmitWithdraw(amount) {
const { account, addPendingTx, sablierAddress, web3 } = this.props;
new web3.eth.Contract(SablierABI, sablierAddress).methods
.withdrawFromStream(, 10).send({ from: recipient }),
.createStream(account, recipient, token, startBlock, stopBlock, paymentWei, blockDelta)
.send({ from: account })
.once("transactionHash", (transactionHash) => {
addPendingTx(transactionHash);
})
.once("confirmation", (confirmationNumber, receipt) => {
this.goToStream(receipt);
})
.once("error", (err) => {
this.onSubmitError(err);
});
}
renderFunds(stream) {
const { t } = this.props;
return (
<div className="stream__funds-container">
<div className="stream__funds-item">
<div className="stream__funds-item__separator" />
<div className="stream__funds-item__label-container">
<span className="stream__funds-item__title-label">
{stream.flow === StreamFlow.IN.name ? t("earned") : t("paid")} {t("soFar")}
</span>
<div className="stream__funds-item__value-container">
<span className="stream__funds-item__value-label">
{stream.funds.paid.toLocaleString()} {stream.token.symbol}
</span>
<TokenLogo className="stream__funds-item__token-logo" address={stream.token.address} size="32px" />
</div>
</div>
</div>
<div className="stream__funds-item" style={{ marginTop: "48px" }}>
<div className="stream__funds-item__separator" />
<div className="stream__funds-item__label-container">
<span className="stream__funds-item__title-label">{t("totalDeposit")}</span>
<div className="stream__funds-item__value-container">
<span className="stream__funds-item__value-label">
{stream.funds.deposit.toLocaleString() || "0"} {stream.token.symbol}
</span>
<TokenLogo className="stream__funds-item__token-logo" address={stream.token.address} size="32px" />
</div>
</div>
</div>
</div>
);
}
renderStat(id, value, icon, tooltip) {
const { t } = this.props;
return (
<div className="stream__stats-item">
<div className="stream__stats-item__icon-container">
<img className="stream__stats-item__icon" alt={t(id)} src={icon} />
</div>
<div className="stream__stats-item__label-container">
<span className="stream__stats-item__title-label">{t(id)}</span>
<span className="stream__stats-item__value-label">{value}</span>
</div>
<div className="spacer" />
<div className="stream__stats-item__info-icon-container" data-tip={tooltip}>
<img className="stream__stats-item__info-icon" alt={t("info")} src={FaInfoCircle} />
<ReactTooltip className="stream__stats-item__tooltip" effect="solid" multiline={true} type="dark" />
</div>
</div>
);
}
renderStream(stream) {
const { t } = this.props;
return (
<div className="stream__left-container">
<div className="stream__stream-container">
<div className="stream__circular-progress-bar-container">
<CustomCircularProgressBar
className="stream__circular-progress-bar"
percentage={stream.funds.ratio}
strokeWidth={6}
>
<span className="stream__circular-progress-bar-percentage-label">{stream.funds.ratio}%</span>
<span className="stream__circular-progress-bar-label">{t("streamed")}</span>
</CustomCircularProgressBar>
</div>
{this.renderFunds(stream)}
</div>
<div className="stream__separator" />
<div className="stream__stats-container">
{stream.flow === StreamFlow.IN.name
? this.renderStat("flow", t("incoming"), FaArrowCircleDown, t("tooltip.flow"))
: this.renderStat("flow", t("outgoing"), FaArrowCircleUp, t("tooltip.flow"))}
{this.renderStat("rate", stream.rate, FaHeartRate, t("tooltip.rate"))}
{this.renderStat("startTime", stream.startTime, FaCalendarAlt, t("tooltip.age"))}
{this.renderStat("stopTime", stream.stopTime, FaCalendarCheck, t("tooltip.remaining"))}
</div>
</div>
);
}
renderActions(stream) {
const { t } = this.props;
return (
<div className="stream__action-container">
<span className="stream__title-label">{t("action", { count: 3 })}</span>
<div className="stream__button-container">
<PrimaryButton
className={classnames(["stream__button", "stream__action-container-button", "primary-button--white"])}
icon={FaTachometer}
label={t("goDashboard")}
labelClassName={classnames("primary-button__label--black")}
onClick={() => this.onClickGoToDashboard()}
/>
{stream.flow === StreamFlow.IN.name ? (
<PrimaryButton
className={classnames(["stream__button", "stream__action-container-button"])}
icon={FaInboxOut}
label={t("withdraw")}
onClick={() => this.setState({ showWithdrawModal: true })}
/>
) : (
<PrimaryButton
className={classnames(["stream__button", "stream__action-container-button"])}
icon={FaPlus}
label={t("newStream")}
onClick={() => this.goToNewStream()}
/>
)}
<PrimaryButton
className={classnames(["stream__button", "stream__action-container-button", "primary-button--yellow"])}
icon={FaStopwatch}
label={t("stopAndWithdraw")}
onClick={() => this.setState({ showStopModal: true })}
/>
</div>
</div>
);
}
renderShare() {
const { t } = this.props;
return (
<div className="stream__share-container">
<span className="stream__title-label">{t("share")}</span>
<div className="stream__button-container" data-tip="Hello There">
<PrimaryButton
className={classnames(["stream__button", "stream__action-container-button", "primary-button--white"])}
icon={FaPaste}
label={t("copyLink")}
labelClassName={classnames("primary-button__label--black")}
onClick={() => this.onClickCopyLink()}
/>
<PrimaryButton
className={classnames(["stream__button", "stream__action-container-button", "primary-button--white"])}
icon={FaTwitterBlack}
label={t("tweetAboutSablier")}
labelClassName={classnames("primary-button__label--black")}
onClick={() => this.onClickTweetAboutSablier()}
/>
<PrimaryButton
className={classnames(["stream__button", "stream__action-container-button", "primary-button--white"])}
icon={FaEnvelope}
label={t("inviteYourFriends")}
labelClassName={classnames("primary-button__label--black")}
onClick={() => this.onClickInviteYourFriends()}
/>
</div>
</div>
);
}
render() {
const { account, blockNumber, rawStreamId, t } = this.props;
const { showStopModal, showWithdrawModal } = this.state;
const streamId = `${account.toLowerCase()}/${rawStreamId}`;
return (
<Query query={GET_STREAM} variables={{ streamId }}>
{({ loading, error, data }) => {
if (loading) return <div className="stream__loader" />;
if (error) return <div className="stream__no-data">{error.message}</div>;
if (!data.stream) return <div className="stream__no-data">{t("noData")}</div>;
const parser = new Parser(data.stream, account, blockNumber, t);
const stream = parser.parse();
return (
<div className="stream">
{this.renderStream(stream)}
<div className="stream__right-container">
{this.renderActions(stream)}
{this.renderShare()}
</div>
{!showWithdrawModal ? null : (
<WithdrawModal
onClose={() => this.setState({ showWithdrawModal: false })}
paid={stream.funds.paid}
tokenSymbol={stream.token.symbol}
withdrawable={stream.funds.withdrawable}
withdrawn={stream.funds.withdrawn}
/>
)}
{!showStopModal ? null : (
<StopModal
deposit={stream.funds.deposit}
onClose={() => this.setState({ showStopModal: false })}
tokenSymbol={stream.token.symbol}
withdrawable={stream.funds.withdrawable}
/>
)}
</div>
);
}}
</Query>
);
}
}
export default connect(
(state) => ({
account: state.web3connect.account,
blockNumber: state.web3connect.blockNumber,
balances: state.web3connect.balances,
// eslint-disable-next-line eqeqeq
isConnected: !!state.web3connect.account && state.web3connect.networkId == (process.env.REACT_APP_NETWORK_ID || 1),
rawStreamId:
sablierAddress: state.addresses.sablierAddress,
web3: state.web3connect.web3,
}),
(dispatch) => ({
addPendingTx: (id) => dispatch(addPendingTx(id)),
push: (path) => dispatch(push(path)),
selectors: () => dispatch(selectors()),
}),
)(withTranslation()(Stream));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment