import React from 'react'; import { Link } from 'react-router-dom' import * as storage from '../../util/storage' import Bar from '../bar' import numberFormatter from '../../util/number-formatter' import * as api from '../../api' const MOBILE_UPPER_WIDTH = 767 const DEFAULT_WIDTH = 1080 const BREAKDOWN_LIMIT = 100 // https://stackoverflow.com/a/43467144 function isValidHttpUrl(string) { let url; try { url = new URL(string); } catch (_) { return false; } return url.protocol === "http:" || url.protocol === "https:"; } export default class PropertyBreakdown extends React.Component { constructor(props) { super(props) let propKey = props.goal.prop_names[0] this.storageKey = 'goalPropTab__' + props.site.domain + props.goal.name const storedKey = storage.getItem(this.storageKey) if (props.goal.prop_names.includes(storedKey)) { propKey = storedKey } if (props.query.filters['props']) { propKey = Object.keys(props.query.filters['props'])[0] } this.state = { loading: true, propKey: propKey, viewport: DEFAULT_WIDTH, breakdown: [], page: 1, moreResultsAvailable: false } this.handleResize = this.handleResize.bind(this); this.fetch = this.fetch.bind(this) this.fetchAndReplace = this.fetchAndReplace.bind(this) this.fetchAndConcat = this.fetchAndConcat.bind(this) } componentDidMount() { window.addEventListener('resize', this.handleResize, false); this.handleResize(); this.fetchAndReplace() if (this.props.query.period === 'realtime') { document.addEventListener('tick', this.fetchAndReplace) } } componentWillUnmount() { window.removeEventListener('resize', this.handleResize, false); document.removeEventListener('tick', this.fetchAndReplace) } handleResize() { this.setState({ viewport: window.innerWidth }); } getBarMaxWidth() { const { viewport } = this.state; return viewport > MOBILE_UPPER_WIDTH ? "16rem" : "10rem"; } fetch({concat}) { if (!this.props.query.filters['goal']) return api.get(`/api/stats/${encodeURIComponent(this.props.site.domain)}/property/${encodeURIComponent(this.state.propKey)}`, this.props.query, {limit: BREAKDOWN_LIMIT, page: this.state.page}) .then((res) => { let breakdown = concat ? this.state.breakdown.concat(res) : res this.setState(() => ({ loading: false, breakdown: breakdown, moreResultsAvailable: res.length >= BREAKDOWN_LIMIT })) }) } fetchAndReplace() { this.fetch({concat: false}) } fetchAndConcat() { this.fetch({concat: true}) } loadMore() { this.setState({loading: true, page: this.state.page + 1}, this.fetchAndConcat.bind(this)) } renderUrl(value) { if (isValidHttpUrl(value.name)) { return ( ) } return null } renderPropContent(value, query) { return ( { value.name } { this.renderUrl(value) } ) } renderPropValue(value) { const query = new URLSearchParams(window.location.search) query.set('props', JSON.stringify({[this.state.propKey]: value.name})) const { viewport } = this.state; return (
{this.renderPropContent(value, query)}
{numberFormatter(value.unique_conversions)} { viewport > MOBILE_UPPER_WIDTH ? ( {numberFormatter(value.total_conversions)} ) : null } {numberFormatter(value.conversion_rate)}%
) } changePropKey(newKey) { storage.setItem(this.storageKey, newKey) this.setState({propKey: newKey, loading: true, breakdown: [], page: 1, moreResultsAvailable: false}, this.fetchAndReplace) } renderLoading() { if (this.state.loading) { return
} else if (this.state.moreResultsAvailable && this.props.query.period !== 'realtime') { return (
) } } renderBody() { return this.state.breakdown.map((propValue) => this.renderPropValue(propValue)) } renderPill(key) { const isActive = this.state.propKey === key if (isActive) { return
  • {key}
  • } else { return
  • {key}
  • } } render() { return (
    Breakdown by:
    { this.renderBody() } { this.renderLoading()}
    ) } }