<div id="app"></div>

<footer>
  <p>
    Created with <i class="fa fa-heart"></i> by
    <a target="_blank" href="https://florin-pop.com">Florin Pop</a>
    - Learn how I built it
    <a target="_blank" href="https://florin-pop.com/blog/2019/05/countdown-built-with-react/">in this post</a>
  </p>
</footer>
@import url('https://fonts.googleapis.com/css?family=Lato');

* {
  box-sizing: border-box;
}

body {
  background-image: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
  
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;

  min-height: 100vh;
  font-family: 'Lato', sans-serif;
  margin: 0;
}

h1 {
  letter-spacing: 2px;
  text-align: center;
  text-transform: uppercase;
}

.countdown-wrapper {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
}

.countdown-item {
  color: #111;
  font-size: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
    line-height: 30px;
  margin: 10px;
    padding-top: 10px;
  position: relative;
  width: 100px;
  height: 100px;
}

.countdown-item span {
  color: #333;
  font-size: 12px;
  font-weight: 600;
  text-transform: uppercase;
}

.countdown-svg {
  position: absolute;
  top: 0;
  left: 0;
  width: 100px;
  height: 100px;
}

footer {
    background-color: #222;
    color: #fff;
    font-size: 14px;
    bottom: 0;
    position: fixed;
    left: 0;
    right: 0;
    text-align: center;
    z-index: 999;
}

footer p {
    margin: 10px 0;
}

footer i {
    color: red;
}

footer a {
    color: #3c97bf;
    text-decoration: none;
}
class Countdown extends React.Component {
  state = {
    days: undefined,
    hours: undefined,
    minutes: undefined,
    seconds: undefined
  }
  
  componentDidMount() {
    this.interval = setInterval(() => {
      const { timeTillDate, timeFormat } = this.props;
      const then = moment(timeTillDate, timeFormat);
      const now = moment();
      const countdown = moment(then - now);
      const days = countdown.format('D');
      const hours = countdown.format('HH');
      const minutes = countdown.format('mm');
      const seconds = countdown.format('ss');

      this.setState({ days, hours, minutes, seconds });
    }, 1000);
  }

  componentWillUnmount() {
    if(this.interval) {
      clearInterval(this.interval);
    }
  }
  
  render() {
    const { days, hours, minutes, seconds } = this.state;
    const daysRadius = mapNumber(days, 30, 0, 0, 360);
    const hoursRadius = mapNumber(hours, 24, 0, 0, 360);
    const minutesRadius = mapNumber(minutes, 60, 0, 0, 360);
    const secondsRadius = mapNumber(seconds, 60, 0, 0, 360);

    if(!seconds) {
      return null;
    }
    
    return (
      <div>
        <h1>Countdown</h1>
        <div className='countdown-wrapper'>
          {days && (
            <div className='countdown-item'>
              <SVGCircle radius={daysRadius} />
              {days} 
              <span>days</span>
            </div>
          )}
          {hours && (
            <div className='countdown-item'>              
              <SVGCircle radius={hoursRadius} />
              {hours} 
              <span>hours</span>
            </div>
          )}
          {minutes && (
            <div className='countdown-item'>
              <SVGCircle radius={minutesRadius} />
              {minutes} 
              <span>minutes</span>
            </div>
          )}
          {seconds && (
            <div className='countdown-item'>
              <SVGCircle radius={secondsRadius} />
              {seconds} 
              <span>seconds</span>
            </div>
          )}
        </div>
      </div>
    );
  }
}

const SVGCircle = ({ radius }) => (
  <svg className='countdown-svg'>
    <path fill="none" stroke="#333" stroke-width="4" d={describeArc(50, 50, 48, 0, radius)}/>
  </svg>
);

ReactDOM.render(
  <Countdown 
    timeTillDate="05 26 2019, 6:00 am" 
    timeFormat="MM DD YYYY, h:mm a" 
  />, 
  document.getElementById('app')
);

// From stackoverflow: https://stackoverflow.com/questions/5736398/how-to-calculate-the-svg-path-for-an-arc-of-a-circle
function polarToCartesian(centerX, centerY, radius, angleInDegrees) {
  var angleInRadians = (angleInDegrees-90) * Math.PI / 180.0;

  return {
    x: centerX + (radius * Math.cos(angleInRadians)),
    y: centerY + (radius * Math.sin(angleInRadians))
  };
}

function describeArc(x, y, radius, startAngle, endAngle){

    var start = polarToCartesian(x, y, radius, endAngle);
    var end = polarToCartesian(x, y, radius, startAngle);

    var largeArcFlag = endAngle - startAngle <= 180 ? "0" : "1";

    var d = [
        "M", start.x, start.y, 
        "A", radius, radius, 0, largeArcFlag, 0, end.x, end.y
    ].join(" ");

    return d;       
}

// Stackoverflow: https://stackoverflow.com/questions/10756313/javascript-jquery-map-a-range-of-numbers-to-another-range-of-numbers
function mapNumber(number, in_min, in_max, out_min, out_max) {
  return (number - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
View Compiled

External CSS

  1. https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.8.0/css/all.min.css

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js
  2. https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js
  3. https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js