css Audio - Active file-generic CSS - Active Generic - Active HTML - Active JS - Active SVG - Active Text - Active file-generic Video - Active header Love html icon-new-collection icon-person icon-team numbered-list123 pop-out spinner split-screen star tv

Pen Settings

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

You're using npm packages, so we've auto-selected Babel for you here, which we require to process imports and make it all work. If you need to use a different JavaScript preprocessor, remove the packages in the npm tab.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Use npm Packages

We can make npm packages available for you to use in your JavaScript. We use webpack to prepare them and make them available to import. We'll also process your JavaScript with Babel.

⚠️ This feature can only be used by logged in users.

Code Indentation

     

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

HTML Settings

Here you can Sed posuere consectetur est at lobortis. Donec ullamcorper nulla non metus auctor fringilla. Maecenas sed diam eget risus varius blandit sit amet non magna. Donec id elit non mi porta gravida at eget metus. Praesent commodo cursus magna, vel scelerisque nisl consectetur et.

            
              <h1 class="panel panel-primary">Done <small class="h1-small">(for now)</small></h1>
<ul>
  <li style="list-style: none; margin: 3rem 0 1rem; outline: 1px dotted green; padding: 0.1rem 0.25rem 0.1rem 1.5rem;"><h4>The code in this Pen is a fully working timer supporting multiple timers and a snooze/dismiss modal, among other features.</h4>
<h4>The full standalone version of this Web App is also <a href="https://github.com/KDCinfo/done-for-now">hosted on GitHub</a>, <a href="https://kdcinfo.github.io/done-for-now/">run via GitHub Pages</a>, and <a href="https://travis-ci.org/KDCinfo/done-for-now">deployed via Travis CI</a>.</h4></li>
</ul>
<p>
    "Done (for now)" is a multi-'timer' web app with custom snooze and a one-at-a-time notification queue.
    This is my second React web app, which I'm hoping will become my first React Native (mobile) app <small>(which is why I haven't done much on this app's responsiveness)</small>.
</p>
<div id="root"></div>
            
          
!
            
              body {
/*   overflow-x: hidden; */
}
#root {
/*   min-width: 800px; */
  outline: 1px dotted blue;
  padding: 2rem 0 1rem;
}
h1,
h1.panel{
  color: #3377DD;
  margin: 0 0rem 1rem;
  padding: 1rem 1rem 0;
}
  h1 small.h1-small {
    color: #3377DD;
  }
h2 {
  color: #DD7733;
  margin: 0 0 1rem;
  padding: 0;
  outline: 0px solid red;
}
  .ul-features h2 {
    margin: 1rem 0 0 1rem;
  }
h4 {
  line-height: 1.4;
}
p {
  outline: 0px solid green;
  padding: 0.5rem 1.25rem 0;
}
/* Input Labels - 3 of them (1 hidden) */

form > div {
  white-space: nowrap;
}
  label,
  div.label {
    display: inline-block;
    font-weight: normal;
    margin: 0;
    padding: 0 1rem .75rem 0;
    text-align: right;
    white-space: normal;
    width: 9rem;
  }

/* Form (left/top) | Timer Listing (right/bottom) */
/* @TODO: Add Responsive for XS vs SM+ (hor/vert) */

/* @media */

.width-50p {
  display: inline-block;
  margin: 0 0 0 1rem;
  max-width: 48%;
  outline: 0px solid red;
  padding: 0;
  vertical-align: top;
}
  .timer-list {
    margin-top: 2.5rem;
  }
  ul {
    margin: 0;
    outline: 0px solid red;
    width: 80%;
  }
    li.padTopLi {
      margin-top: 0.5rem;
    }
    li.padTopLi2 {
      margin-top: 1.5rem;
    }

/* BUTTONS - TIMER FORM */

  /* Button - Set Timer (big) */
  /* Button - Delete (small)  */

button,
button.btn {
  background-color: #ffffff;
  border: 1px solid #3377dd;
  box-shadow: 3px 3px 7px;
  cursor: pointer;
}
  .add-button-div {
    margin-top: 0.75rem;
    text-align: right;
  }
  button.btn-xs {
    font-size: 0.85rem;
    padding: .3rem .6rem;
    box-shadow: 1px 1px 3px;
    margin: 0.25rem 0;
    line-height: 1;
  }
  button:hover,
  button.btn:hover,
  button:focus,
  button.btn:focus {
    background-color: #b3d7ff;
  }
  button:active,
  button.btn:active {
    background-color: #b3d7ff;
    box-shadow: 0;
  }

/* TIMER LIST - Table */

.timers-div table th,
.timers-div table td {
  padding: 0 0.5rem;
}
  .timers-div table tr th {
    line-height: 1.1;
    vertical-align: bottom;
    padding-bottom: 0.5rem;
  }
  .timers-div table td {
    border-top: 1px solid rgba(125,175,255,.75);
  }
  .timers-div table td.text-center {
    text-align: center;
  }

/* MODAL */

.modal-alert {
  margin: 0 auto;
  text-align: center;
}
  .modal-content {
    background-color: rgba(255, 255, 255, 0.7);
    border: 5px solid #ffffff;
    border-radius: 25px;
  }
    .modal-title,
    .modal-subtitle {
      color: #3377DD;
      font-size: 26px;
    }
      .modal-title {
        font-size: 30px;
        font-weight: bold;
      }
    .modal-alert button,
    .modal-alert button.btn{
      border-radius: 60px;
      font-size: 400%;
      font-weight: bold;
      height: 9rem;
      line-height: 1.2;
      margin: 1.5rem 1rem;
      text-shadow: 2px 2px 9px red;
      width: 80%;
    }
    .modal-alert button:hover, 
    .modal-alert button.btn:hover, 
    .modal-alert button:focus, 
    .modal-alert button.btn:focus {
      background-position: center;
      border: 7px solid darkblue;
      border-radius: 60px;
      outline: 0;
    }

/* UTILITY / MISCELLANEOUS */

.hidden {
  visibility: hidden;
}
.nowrap {
  white-space: nowrap;
}
            
          
!
            
              // Done (for now)

// A React-based Web App Timer with custom snooze and a one-at-a-time notification queue.

// 'Done (for now)' provides timer alerts (via a modal) with Snooze/Done/Disable options (and an adjustable snooze delay).
// It also provides a custom notification 'queue' which accounts for overlapping/simultaneous alerts.

// @DONE: Done items are at the bottom of this file
    // Last updated 2017-07-22
    // Future updates will be done via GitHub.
    // GitHub Source:     https://github.com/KDCinfo/done-for-now
    // GitHub Pages Demo: https://kdcinfo.github.io/done-for-now/
    // Keith D Commiskey: https://kdcinfo.com/resume

// @NOTE: Notes left in here were done so for future reference

// @TODO: Convert to React Native (learn React Native)
    // This will be done via another GitHub Repository
    // I mean I guess: Maybe this project will suffice?
    // Don't know yet 'cause I don't know Native yet :)

;console.clear();

/*
    Storage (local; client-side [window])
    https://developer.mozilla.org/en-US/docs/Web/API/Storage
*/
// export const setStorageItem = () => {}
const setStorageItem = (storage, key, value) => {
    try {
        storage.setItem(key, value)
    } catch(e) {
        console.error(e)
    }
}
// export const getStorageItem = () => {}
const getStorageItem = (storage, key) => {
    try {
        return storage.getItem(key)
    } catch(e) {
        console.error(e)
        return null
    }
}

class TimerBox extends React.Component {
    constructor(props) {
        super(props)

        const storedSnoozeTime = getStorageItem(localStorage, 'snoozeTime'),
              stateSnoozeTime = storedSnoozeTime ? storedSnoozeTime : 3,
              storedTimerList = getStorageItem(localStorage, 'timerList'),
              stateTimerList = (storedTimerList) ? JSON.parse(storedTimerList) : []

        this.state = {
            timerList: stateTimerList,  // A list of timers: { id: 0, title: '', time: '00:00', cycle: 0 }
            timeoutList: [],            // A list of active timers -- with Timeout ID: { id: 0, timer: 0 }
                                        // Also includes a list of 'display timers' (sibling setTimeouts).
            timeoutQueue: [],           // A list of completed timeouts going through the 'modal' process.
            timerDisplayList: [],       // 1-second timeouts that update set (stateless) <TimeDisplay />s.
            titleCount: 20,
            titleTemp: 'Watch my show!',
            countHours: 24,
            countMinutes: 60,
            stepCountMinutes: 5,
            snoozeTime: stateSnoozeTime,
            entryCycleList: ['daily','hourly','every minute'],
            showModal: false,
            modalTitle: '',
            modalTimerId: '',
        }
        this.setSnooze = this.setSnooze.bind(this)
        this.setTimer = this.setTimer.bind(this)
        this.setTimerCallback = this.setTimerCallback.bind(this)
        this.addRemoveTimeout = this.addRemoveTimeout.bind(this)
        this.toggleTimeout = this.toggleTimeout.bind(this)
        this.removeTimer = this.removeTimer.bind(this)
        this.getLastId = this.getLastId.bind(this)
        this.deleteTimeout = this.deleteTimeout.bind(this)
        this.createTimeout = this.createTimeout.bind(this)
        this.timerSnooze = this.timerSnooze.bind(this)
        this.timerReset = this.timerReset.bind(this)
        this.timerDisable = this.timerDisable.bind(this)
        this.showModal = this.showModal.bind(this)
        this.getTimeDiff = this.getTimeDiff.bind(this)
        this.getTimeDiffUpdate = this.getTimeDiffUpdate.bind(this)
        this.addToTimeoutQueue = this.addToTimeoutQueue.bind(this)
        this.removeFromTimeoutQueue = this.removeFromTimeoutQueue.bind(this)
        this.checkTimeoutQueue = this.checkTimeoutQueue.bind(this)
    }
    initializeState() {
        // Create new 'timeoutList' from stored 'timerList'
            // Go through each [timerList] entry and, if 'active', execute addRemoveTimeout(entryId, 'add')

        // On 'componentWillUnmount', empty [timeoutList]
            // Clear setTimeouts, but "don't" set 'timer' entry to non-active
            // Must clear these because setTimeout() will no longer be active

            // Don't think 'componentWillUnmount' functionality is necessary...
                // If 'componentDidMount' is being run, then 'this.state' will be (re)initialized as well,
                // which will zero out [] both 'timeoutList' and 'timeoutQueue'

        const storedTimerList = getStorageItem(localStorage, 'timerList'),
              stateTimerList = (storedTimerList) ? JSON.parse(storedTimerList) : []

        // 'this.state.timeoutList' should already be empty [] ( per constructor's this.state = {} )
        // 'this.state.timeoutQueue' should already be empty [] ( per constructor's this.state = {} )

        if (stateTimerList.length > 0) {
            stateTimerList.forEach( (elem) => {
                if (elem.active === true) {
                    setTimeout( () => {                         // WHen these are not staggered, only 1 shows in the 'timerDisplay' code/layout.
                        this.addRemoveTimeout(elem.id, 'add')   // And although setTimeout({},0) fixes this, it is not the best solution.
                    },0)                                        // I just don't know what a more proper solution/approach would be.
                }
            })
        }
    }
    componentDidMount() {
        this.initializeState()
    }

    // Timers   - Can be active/non-active [id, title, time, cycle, active]
    // Timeouts - Contains only active Timers [Timer ID, setTimeout ID]

    // Timer - Add
        // this.setTimer(...params) // From <form> submit
        // --> this.addRemoveTimeout --> (lastId+1)

    // Timer - Toggle Activation
        // ID passed in

        // Timer - ON
            // Create/Start Timeout: this.addRemoveTimeout(timerId, 'add')
            // Toggle Timer on: this.toggleTimeout('on')

        // Timer - OFF
            // Remove/Clear Timeout: this.addRemoveTimeout(timerId, 'remove')
            // Toggle Timer off: this.toggleTimeout('off')

    // Timer - Remove
        // From <Timers /> Listing
        // If timer is active: this.addRemoveTimeout(timerId, 'remove')
        // Remove from timerList: this.removeTimer(timerId)

        // Text PADDING: Pre-ES8 (pre-ES2017) (i.e., the old-fashioned way)
            // entryHoursPad = entryHours.toString().length === 1 ? '0'+entryHours : entryHours,
            // entryMinutesPad = entryMinutes.toString().length === 1 ? '0'+entryMinutes : entryMinutes,

    setTimer(entryTitle, entryHours, entryMinutes, entryCycle) {
        const entryHoursPad = entryHours.toString().padStart(2, '0'),
              entryMinutesPad = entryMinutes.toString().padStart(2, '0'),
              lastIdx = this.getLastId(),
              nextId = this.state.timerList.length === 0 ? 0 : (lastIdx+1),
              timerList = this.state.timerList.concat({
                  id: nextId,
                  title: entryTitle,
                  time: entryHoursPad + ':' + entryMinutesPad,
                  cycle: entryCycle,
                  active: false
              })
        this.setState({ timerList }, () => {
            setStorageItem(localStorage, 'timerList', JSON.stringify(timerList))
            this.setTimerCallback()
        })
    }
    getLastId() {
        const lastId = this.state.timerList.reduce( (agg, curObj) => (curObj.id > agg) ? curObj.id : agg, 0 )

        return lastId
    }
    setTimerCallback() {
        const lastId = this.getLastId()

        this.toggleTimeout(lastId, 'on') // 'addRemoveTimeout' is called from 'toggleTimeout'
    }
    removeTimer(timerId) {
        const entryId = parseInt(timerId, 10),
              newTimerList = this.state.timerList,
              timerEntry = newTimerList.find( elem => (elem.id === entryId) )

        if (timerEntry.active === true) {
            this.addRemoveTimeout(entryId, 'remove')
        }

        newTimerList.forEach( (elem, idx) => {
            if (elem.id === entryId) {
                newTimerList.splice(idx, 1)
            }
        })
        this.setState({ timerList: newTimerList }, () => {
            setStorageItem(localStorage, 'timerList', JSON.stringify(newTimerList))
        })
    }
    addRemoveTimeout(timerId, whichTask) {
        // This method has 2 entry points:
            // removeTimer(timerId) // ID is passed in (from Timer listing)
            // toggleTimeout()      // ID is passed in (has 3 entry points)

        const entryId = timerId

        if (whichTask === 'remove') {
            this.deleteTimeout(entryId)
        } else if (whichTask === 'update') {
            this.updateTimeout(entryId)         // no 2nd param        (setTimeout delay is based on current time)
        } else if (whichTask === 'snooze') {
            this.updateTimeout(entryId, true)   // 2nd param = snooze  (setTimeout delay is set based on state.snoozeTime)
        } else {
            this.createTimeout(entryId)         //                     (setTimeout delay is set based on <form> submit values and current time)
        }
    }
    deleteTimeout(entryId) {
        // This method has 1 entry point:
            // addRemoveTimeout()

        let newTimeoutList = this.state.timeoutList,
            newTimerDisplayList = this.state.timerDisplayList,
            timeoutTimerId

        // timeoutList[] will have 2 entries for each timeout
            // [
            //     {id: 0, timer: 15}, <-- Modal-prompt setTimeout ('pop-up alerts')
            //     {id: 0, timer: 24}, <-- Every-second setTimeout ('visual counter')
            //     {id: 1, timer: 32}, <-- Modal-prompt setTimeout
            //     {id: 1, timer: 45}  <-- Every-second setTimeout
            // ]

            newTimeoutList.forEach( (elem, idx) => {
                if (elem.id === entryId) {
                    timeoutTimerId = elem.timer
                    newTimeoutList.splice(idx, 1)
                    window.clearTimeout(elem.timer)
                }
            })
            this.setState({timeoutList: newTimeoutList})

        // timerDisplayList[]
            // [
            //     {id: 24, destination: targetDateInMilliseconds}, <-- 'id' is this sibling timeout's Timer ID; 'destination' is current date/time plus timeDiff
            //     {id: 45, destination: targetDateInMilliseconds}  <-- 'id' is this sibling timeout's Timer ID; 'destination' is current date/time plus timeDiff
            // ]

            newTimerDisplayList.forEach( (elem, idx) => {
                if (elem.id === timeoutTimerId) {
                    newTimerDisplayList.splice(idx, 1)
                    window.clearTimeout(timeoutTimerId)
                }
            })
            this.setState({timerDisplayList: newTimerDisplayList})
    }
    createTimeout(entryId) {
        // This method has 1 entry point:
            // addRemoveTimeout()

        const newTimerList = this.state.timerList,
              timerEntry = newTimerList.find( elem => (elem.id === entryId) ),
              timerHour = parseInt(timerEntry.time.substr(0,2), 10),
              timerMinute = parseInt(timerEntry.time.substr(3,2), 10),
              timerCycle = timerEntry.cycle,
              thisTimeDiff = this.getTimeDiff(timerHour, timerMinute, timerCycle)


        let newTimeoutList = this.state.timeoutList,
            newTimerDisplayList = this.state.timerDisplayList,
            newTimeout,
            newTimeoutEntry,
            newTimerDisplayEntry,
            targetTime

        // setTimeout function will subtract current time from target time and use as ({setTimeout's}, wait) time

        newTimeout = setTimeout( () => {
            // Add to timeoutQueue
                // 1. Add to queue[] (array) when setTimeout time is up;
                // 2. Remove from queue[] when closing modal;
                // 3. Run queueCheck() to see if any others have entered since modal was up.
            this.addToTimeoutQueue(entryId)
            // this.setState(...timeoutParams, this.showModal) // <-- Moving this to [addToTimeoutQueue()]
                // this.setState({ modalTitle: timerEntry.title, modalTimerId: entryId }, this.showModal)
                // For native apps, modal should work fine (I presume you can hook into the system's messaging system)

            // For web-based, might consider using window.confirm() which uses browser internal message notification system
                // (it'll alert you if you're on another tab)
                // window.confirm(timerEntry.title)
        }, thisTimeDiff)
        // }, 1000)

        targetTime = (Date.now() + thisTimeDiff)
        newTimerDisplayEntry = { id: newTimeout, destination: targetTime }
        newTimerDisplayList = newTimerDisplayList.concat(newTimerDisplayEntry)
        this.setState({ timerDisplayList: newTimerDisplayList })

//                 // timeoutList[] will have 2 entries for each timeout
//                     // [
//                     //     {id: 0, timer: 15}, <-- Modal-prompt setTimeout ('pop-up alerts')
//                     //     {id: 0, timer: 24}, <-- Every-second setTimeout ('visual counter')
//                     //     {id: 1, timer: 32}, <-- Modal-prompt setTimeout
//                     //     {id: 1, timer: 45}  <-- Every-second setTimeout
//                     // ]
//                 // this.state.timerDisplayList[]
//                     // [
//                     //     {id: 24, destination: now + timeDiff}, <-- 'destination' date/time minus current date/time
//                     //     {id: 45, destination: now + timeDiff}  <-- 'destination' date/time minus current date/time
//                     // ]
//                 // 1499929200000 + 45000 (45 seconds; in future (in milliseconds))

//                 const timerDisplayListIndex = newTimerDisplayList.findIndex(x => x.id === newTimeout),
//                       tmpTimerDisplayList = [
//                            ...newTimerDisplayList.slice(0,timerDisplayListIndex),
//                            { id: newTimeout, destination: (timerDisplayList[timerDisplayListIndex].destination - Date.now()) },
//                            ...newTimerDisplayList.slice(timerDisplayListIndex+1)
//                       ]

//                 this.setState({ timerDisplayList: tmpTimerDisplayList })

            // this.getTimeDiff() // Returns milliseconds between [current time] and [current time + set time + cycle]

        newTimeoutEntry = { id: entryId, timer: newTimeout }
        newTimeoutList = newTimeoutList.concat(newTimeoutEntry)
        this.setState({ timeoutList: newTimeoutList })
    }
    addToTimeoutQueue(entryId) {
        let tmpTimeoutQueue = this.state.timeoutQueue

        tmpTimeoutQueue.push(entryId)
        this.setState({timeoutQueue: tmpTimeoutQueue}, this.checkTimeoutQueue)
    }
    removeFromTimeoutQueue(entryId) {
        let tmpTimeoutQueue = this.state.timeoutQueue

        tmpTimeoutQueue.forEach( (elem, idx) => {
            if (elem === entryId) {
                tmpTimeoutQueue.splice(idx, 1)
            }
        })
        this.setState({timeoutQueue: tmpTimeoutQueue}, this.checkTimeoutQueue)
    }
    checkTimeoutQueue() {
        // get queue, get first[0] id in queue
        // set [state]modal contents (which will show modal with showModal: true)

        let tmpTimeoutQueue = this.state.timeoutQueue
        if (tmpTimeoutQueue.length > 0) {
            this.setModal(tmpTimeoutQueue[0])
        }
    }
    setModal(entryId) {
        const timerEntry = this.state.timerList.find( entry => (entry.id === entryId) )
        this.setState({
            modalTitle: timerEntry.title,
            modalTimerId: timerEntry.id
        }, this.showModal)
    }
    updateTimeout(entryId, isSnooze) {
        // This method has 2 calls from 1 entry point:
            // addRemoveTimeout()

        const newTimerList = this.state.timerList,
              timerEntry = newTimerList.find( elem => (elem.id === entryId) ),
              timerHour = parseInt(timerEntry.time.substr(0,2), 10),
              timerMinute = parseInt(timerEntry.time.substr(3,2), 10),
              timerCycle = timerEntry.cycle,
              thisTimeDiffUpdate = this.getTimeDiffUpdate(timerHour, timerMinute, timerCycle),
              thisTimeoutWait = (isSnooze) ? (this.state.snoozeTime * 60 * 1000) : thisTimeDiffUpdate
                                          // [this.state.snoozeTime] is set in {state} as 'minutes', so we need to convert to milliseconds

        let newTimeoutList = this.state.timeoutList,
            newTimerDisplayList = this.state.timerDisplayList,
            newTimeout,
            tmpTimerOldId,
            targetTime

        newTimeout = setTimeout( () => {
            // this.setState({ modalTitle: timerEntry.title, modalTimerId: entryId }, this.showModal)
            this.addToTimeoutQueue(entryId)
        }, thisTimeoutWait)

        // this.deleteTimeout(entryId) // No need to delete existing 'timeout': Just update with new Timeout ID (i.e., the results of [newTimeout])

        // UPDATE TIMEOUT LIST

        // Update an Object's properties from within an Array
        newTimeoutList = newTimeoutList.map( (elem, idx) => {
            if (elem.id === entryId) {
                tmpTimerOldId = elem.timer
                elem.timer = newTimeout
            }
            return elem
        })
        this.setState({ timeoutList: newTimeoutList })

        // UPDATE VISUAL COUNTDOWN LIST

        targetTime = (Date.now() + thisTimeoutWait)
        // newTimerDisplayEntry = { id: newTimeout, destination: targetTime }
        // newTimerDisplayList = newTimerDisplayList.concat(newTimerDisplayEntry)

        newTimerDisplayList = newTimerDisplayList.map( elem => {
            if (elem.id === tmpTimerOldId) {
                elem.id = newTimeout
                elem.destination = targetTime
            }
            return elem
        })
        this.setState({ timerDisplayList: newTimerDisplayList })
    }
    getTimeDiffUpdate(tHour, tMinute, timerCycle) {

        let tmpDate = new Date(),
            timerDate = tmpDate.getDate(),
            timerHour = tHour,
            timerMinute = tMinute,
            addDate = 0,
            addHours = 0,
            addMinutes = 0,
            nowDate,
            nowSetTime,
            futureSetTime,
            timeToSetAhead = 0

        const currentMinutes = tmpDate.getMinutes()

        tmpDate.setMilliseconds(0)
        tmpDate.setSeconds(0)

            // 'cycle' === '0: daily'
            // 'cycle' === '1: hourly'
            // 'cycle' === '2: minute'

        if (timerCycle === 0) {
            // tmpDate.setDate(tmpDate.getDate() + 1)

            tmpDate.setMinutes(timerMinute, 0, 0)
            tmpDate.setHours(timerHour)

            if (timerDate === tmpDate.getDate()) {              // 15 = 15
                addDate = 1
            } else {                                            // If not equal, just add a day to current day
                addDate = tmpDate.getDate() + 1
            }

        } else if (timerCycle === 1) {
            // tmpDate.setHours(tmpDate.getHours() + 1)

            // tH   cH -- (tH: timeoutHour, cH: currentHour)
            // 18 < 00 -- addHours = 24 + (timerMHour - tmpDate.getMHours()) + 1
            // 18 = 01 -- addHours = 1
            // 18 > 00 -- addHours = (timerMHour - tmpDate.getMHours()) + 1

            tmpDate.setMinutes(timerMinute, 0, 0)                   // I believe this just zeros it out, and doesn't increment/decrement hours.

            if (timerMinute <= currentMinutes) {

                // if (timerHour < tmpDate.getHours()) {                   // 18 < 21 | 1 < 2
                // } else if (timerHour === tmpDate.getHours()) {          // 18 = 18
                // } else if (timerHour > tmpDate.getHours()) {            // 18 > 15 | 2 > 1
                //     // addHours = (timerHour - tmpDate.getHours()) + 1
                // }
                addHours = 1
            }

        } else if (timerCycle === 2) {
            // tmpDate.setMinutes(tmpDate.getMinutes() + 1)

            addMinutes = 1
        }

        tmpDate.setMinutes(tmpDate.getMinutes() + addMinutes)
        tmpDate.setHours(tmpDate.getHours() + addHours)
        tmpDate.setDate(tmpDate.getDate() + addDate)

        futureSetTime = tmpDate.getTime()                       // Future milliseconds

        nowDate = new Date()
        nowSetTime = nowDate.getTime()                          // Current milliseconds

        timeToSetAhead = futureSetTime - nowSetTime             // Future milliseconds - now() milliseconds +> Target Hours +> Target Minutes

            console.log('Add [Date|Hours|Minutes]', addDate, '|', addHours, '|', addMinutes)

            console.log('[Current Date]', nowDate.toString())
            console.log('[Target Date]', tmpDate.toString())

            console.log('[setTimeout(,milliseconds)]', timeToSetAhead)

        return timeToSetAhead
    }
    getTimeDiff(tHour, tMinute, timerCycle) {

        let timerHour = tHour,
            timerMinute = tMinute,
            tmpDate = new Date(),
            addMinutes = 0,
            addHours = 0,
            nowDate,
            nowSetTime,
            futureSetTime,
            timeToSetAhead = 0

        const currentMinutes = tmpDate.getMinutes()

        tmpDate.setMilliseconds(0)
        tmpDate.setSeconds(0)

        // 'cycle' === '0: daily'
        // 'cycle' === '1: hourly'
        // 'cycle' === '2: minute'

        if (timerMinute < tmpDate.getMinutes()) {               // :30 < :45 | 19 < 20 | 59

            addMinutes = 60 + (timerMinute - tmpDate.getMinutes())

        } else if (timerMinute === tmpDate.getMinutes()) {      // :30 = :30

            if (timerCycle === 0 || timerCycle === 1) {
                addMinutes = 0
            } else {
                addMinutes = 1
            }

        } else if (timerMinute > tmpDate.getMinutes()) {        // :30 > :15

            addMinutes = (timerMinute - tmpDate.getMinutes())
        }

        tmpDate.setMinutes(tmpDate.getMinutes() + addMinutes)

        if (timerHour < tmpDate.getHours()) {                   // 18 < 21

            addHours = 24 + (timerHour - tmpDate.getHours())

        } else if (timerHour === tmpDate.getHours()) {          // 18 = 18 | 3 = (2 + 1)

            // console.log('[...]', timerMinute, '<=', tmpDate.getMinutes(), '<=', currentMinutes)
            // ^---< was having trouble per testing note 3 lines down : Determined bad comparison

            //                         tT      cT
            // 31 "<=" 32 "|" 1  ===  3:31 <= 2:32  ===

            if (timerMinute === currentMinutes) {
                // Changed from [tmpDate.getMinutes()] to [currentMinutes]
                    // Due to test: (current time) 03:52 ==> (target time) 03:53
                    // 1 minute ahead resulted in (+1 hour +1 minute) ahead

                if (timerCycle === 0) {
                    addHours = 24
                } else if (timerCycle === 1) {
                    addHours = 0
                } else {
                    addHours = 0
                }
            } else {
                addHours = 0
            }

        } else if (timerHour > tmpDate.getHours()) {            // 18 > 15

            addHours = (timerHour - tmpDate.getHours())
        }

        tmpDate.setHours(tmpDate.getHours() + addHours)

        futureSetTime = tmpDate.getTime()                       // Future milliseconds

        nowDate = new Date()

            // nowDate.setMilliseconds(0)
                // Don't need to zero this out.

            // nowDate.setSeconds(0)
                // Don't zero
                // Use current seconds to allow for 'same-minute' execution
                // I.e., if timer is set within the current minute,
                    // but current time is 30 seconds before the target 'timerMinute',
                    // it'll wait that first 30 seconds... not a minute and 30 seconds.

            nowSetTime = nowDate.getTime()                      // Current milliseconds

        timeToSetAhead = futureSetTime - nowSetTime             // Future milliseconds - now() milliseconds +> Target Hours +> Target Minutes

            console.log('[getTimeDiff] Add [Hours|Minutes]', addHours, '|', addMinutes)

            console.log('[getTimeDiff] [Current Date]', nowDate.toString())
            console.log('[getTimeDiff] [Target Date]', tmpDate.toString())

            // console.log('[Current milliseconds]', nowSetTime)
            // console.log('[Target milliseconds]', futureSetTime)
            console.log('[getTimeDiff] [setTimeout(,milliseconds)]', timeToSetAhead)

            // [getTimeDiff] AAA double-check with ZZZ (below)     24 0 0 0
            // [getTimeDiff] tmpDate PRE seconds set               Wed Jul 12 2017 00:24:54 GMT-0700 (Pacific Daylight Time)
            // [getTimeDiff] tmpDate POST seconds set              Wed Jul 12 2017 00:24:00 GMT-0700 (Pacific Daylight Time)
            // [getTimeDiff] addMinutes                        36  Wed Jul 12 2017 01:00:00 GMT-0700 (Pacific Daylight Time)
            // [getTimeDiff] addHours                          23  Thu Jul 13 2017 00:00:00 GMT-0700 (Pacific Daylight Time)
            // [getTimeDiff] ZZZ Double check same as AAA          24 0 0 0
            // [tmpDate]                                           Thu Jul 13 2017 00:00:00 GMT-0700 (Pacific Daylight Time)
            // [nowDate]                                           Wed Jul 12 2017 00:24:54 GMT-0700 (Pacific Daylight Time)
            // [nowSetTime]                                        1499844294219
            // [futureSetTime]                                     1499929200000
            // [timeToSetAhead]                                    84905781

        return timeToSetAhead
    }
    toggleTimeout(timerId, onOff) {
        // This method has 4 entry points:
            // setTimerCallback()       // ID is highest
            // <Timer /> checkbox       // ID is passed in
            // <TimerBox /> alert Modal // ID from Modal: (timerReset && timerSnooze) (callbacks)

        console.log('[toggleTimeout]', timerId, onOff)

        if (onOff === 'on') {                           // <Form /> Add -> setTimer() -> setTimerCallback()
            this.addRemoveTimeout(timerId, 'add')
        } else if (onOff === 'off') {                   // timerList[] -> <Timer /> -> checkbox
            this.addRemoveTimeout(timerId, 'remove')
        } else if (onOff === 'snooze') {                // <Modal /> -> Snooze
            this.addRemoveTimeout(timerId, 'snooze')
        } else {                                        // <Modal /> -> Done (for now)
            this.addRemoveTimeout(timerId, 'update')
        }

        // Update Global Timer List - Set timer on/off (active/non-active)
            // Timer checkbox display (in Timer Listing)
            //
        if (onOff !== 'update' && onOff !== 'snooze') {
                                    // Added this condition because don't think we need to run this entire section of code if it's an 'update'
                                    // ('active' state should already be 'true' -- Just need to update the timer's new Timout ID)
            const entryIdx = timerId,
                  newTimerList = this.state.timerList.map( (elem, idx) => {
                    if (elem.id === entryIdx) {
                        elem.active = (onOff === 'on') ? true : false
                        // elem.active = (onOff === 'on' || onOff === 'update') ? true : false
                    }
                    return elem
                  })
            this.setState({ timerList: newTimerList }, () => {
                setStorageItem(localStorage, 'timerList', JSON.stringify(newTimerList))
            })
        }
    }
    timerSnooze(entryId) {
        //
        this.setState({ showModal: false }, () => { // This will turn off the modal,
            this.removeFromTimeoutQueue(entryId)    //  then remove the entry from the timeoutQueue.
            this.toggleTimeout(entryId, 'snooze')   //  then this will setup a new setTimeout, which, when done, will add this entry back into the timeoutQueue
                                                        // This should be run after the removal of the entry from the timeoutQueue (else it'll remove this entry).
                                                        // This should not pose an issue unless the setTimeout execution is less than the few milliseconds
                                                        // it takes for the 'removeFromTimeoutQueue()' method above to remove it from the queue first.
        })
    }
    timerReset(entryId) {
        //
        this.setState({ showModal: false }, () => { // This will turn off the modal,
            this.removeFromTimeoutQueue(entryId)    //  then remove the entry from the timeoutQueue.
            this.toggleTimeout(entryId, 'update')   //  then this will setup a new setTimeout, which, when done, will add this entry back into the timeoutQueue
                                                        // This should be run after the removal of the entry from the timeoutQueue (else it'll remove this entry).
                                                        // This should not pose an issue unless the setTimeout execution is less than the few milliseconds
                                                        // it takes for the 'removeFromTimeoutQueue()' method above to remove it from the queue first.
        })
    }
    timerDisable(entryId) {
        //
        this.setState({ showModal: false }, () => {
            this.toggleTimeout(entryId, 'off')
            this.removeFromTimeoutQueue(entryId)
        })
    }
    setSnooze(snoozeTime) {
        //
        this.setState(
            { snoozeTime: snoozeTime },
            () => {
                setStorageItem(localStorage, 'snoozeTime', snoozeTime)
                console.log('[setSnooze] Snooze time set to:', this.state.snoozeTime)
            }
        )
    }
    showModal() {
        this.setState({ showModal: true })
    }
    render() {
        const configSettings = {
            titleCount: this.state.titleCount,
            titleTemp: this.state.titleTemp,
            countHours: this.state.countHours,
            countMinutes: this.state.countMinutes,
            stepCountMinutes: this.state.stepCountMinutes,
            entryCycleList: this.state.entryCycleList
        }
        return (
            <div>
                <div className="width-50p">
                    <SettingsForm setTimer={this.setTimer} {...configSettings} />
                </div>
                <div className={`width-50p timer-list ${this.state.timerList.length === 0 && 'hidden'}`}>
                    <Timers
                        removeTimer={this.removeTimer}
                        toggleTimeout={this.toggleTimeout}
                        timerList={this.state.timerList}
                        timeoutList={this.state.timeoutList}
                        timerDisplayList={this.state.timerDisplayList}
                        entryCycleList={this.state.entryCycleList}
                    />
                </div>
                <div className="ul-features">
                    <ul>
                        <li className="padTopLi2">Snooze delay time (in minutes; for future snoozes):&nbsp;
                            <SnoozeForm snoozeTime={this.state.snoozeTime} setSnooze={this.setSnooze} />
                        </li>
                        <li className="padTopLi">When a timer is created, the timer will be initially set to the next available time from when the time is set based on the 'cycle' selection.</li>
                    </ul>
                    <h2>App Features</h2>
                    <ul>
                        <li className="padTopLi2">"<strong>Done (for now)</strong>" provides a list of all timers (both active and disabled).</li>
                        <li>Timers provide the option to set recurring alerts (based on 'daily', 'hourly', and 'every minute' increments).<br/>
                            &nbsp;&nbsp;<u>Note:</u> Although all timers are initially set to be recurring, they can simply be 'Disabled' when the alert pops up, and can be disabled manually at any time.</li>
                        <li>The snooze option is adjustable (between 1-15 minutes).</li>
                        <li>"Done (for now)" also has a 'timer queue' to account for overlapping timer alerts.</li>
                        <li>Both the [Timer List] and the custom 'Snooze Time' are saved to your local browser storage.
                            <ul>
                                <li>If the page is refreshed, all timers will be recreated from this saved storage.</li>
                                <li>Any timers in 'snooze' state will be reset to their next default time (i.e., snoozes aren't saved).</li>
                            </ul>
                        </li>
                        <li className="btn-warning">@TODO: Convert to React Native (<b><i>learn React Native</i></b>)</li>
                    </ul>
                </div>
                <TimerAlertPrompt
                    show={this.state.showModal}
                    timerList={this.state.timerList}
                    entryCycleList={this.state.entryCycleList}
                    modalTimerId={this.state.modalTimerId}
                    modalTitle={this.state.modalTitle}
                    timerReset={this.timerReset}
                    timerDisable={this.timerDisable}
                    timerSnooze={this.timerSnooze}
                    snoozeTime={this.state.snoozeTime}
                    // hideDelete={false}
                    // userDelete={this.userDelete}
                />
             </div>
        )
    }
}
class SnoozeForm extends React.Component {
    constructor(props) {
        super(props)
        this.updateSnooze = this.updateSnooze.bind(this)
    }
    updateSnooze(e) {
        this.props.setSnooze(parseInt(e.target.value, 10))
    }
    render() {
        const snoozeTime = parseInt(this.props.snoozeTime, 10)

        return (
            <select onChange={this.updateSnooze}>
                { Array.from(Array(15), (e,i)=>(i+1)).map( entry => {
                    return (
                        <option selected={snoozeTime === entry}>{entry}</option>
                    )
                }) }
            </select>
        )
    }
}
class Timers extends React.Component {
    render() {
        // console.log('[Timers] 1:', this.props.timeoutList)
        // console.log('[Timers] 2:', this.props.timerDisplayList)
        return (
            <div className="timers-div">
                <table>
                    <tr>
                        <th>Title</th>
                        <th>Start<br/>Time</th>
                        <th>Cycle</th>
                        <th>On/Off</th>
                        <th className="text-center">Time Until</th>
                        <th>&nbsp;</th>
                    </tr>
                    { this.props.timerList.map( (entry, idx) =>
                        <Timer
                            key={entry.key}
                            entry={entry}
                            timeoutList={this.props.timeoutList}
                            timerDisplayList={this.props.timerDisplayList}
                            entryCycleList={this.props.entryCycleList}
                            toggleTimeout={this.props.toggleTimeout}
                            removeTimer={this.props.removeTimer}
                        />
                    ) }
                </table>
            </div>
        )
    }
}
class TimeDisplay extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            secondsElapsed: 0,
            interval: 0,
            tTimeDisplay: ''
        }
        this.tick = this.tick.bind(this)
    }
    tick() {
        // this.setState({secondsElapsed: this.state.secondsElapsed + 1})
        let tDateDiff,
            tDateHr,
            tDateMin,
            tDateSec,
            tmpTimeDisplay

        if (this.props.targetTime === 0) {
            this.setState({ tTimeDisplay: '' })
        } else {
            tDateDiff = (this.props.targetTime - Date.now())
            // tDateMin = Math.floor(tDateDiff / 60000)
            // tDateSec = ((tDateDiff % 60000) / 1000).toFixed(0)
            tDateSec = parseInt((tDateDiff / 1000) % 60, 10)
            tDateMin = parseInt((tDateDiff / (1000 * 60)) % 60, 10)
            tDateHr = parseInt((tDateDiff / (1000 * 60 * 60)) % 24, 10)

            tDateHr = (tDateHr < 10) ? "0" + tDateHr : tDateHr
            tDateMin = (tDateMin < 10) ? "0" + tDateMin : tDateMin
            tDateSec = (tDateSec < 10) ? "0" + tDateSec : tDateSec

            // return minutes + ":" + (seconds < 10 ? '0' : '') + seconds
            // return (seconds == 60 ? (minutes+1) + ":00" : minutes + ":" + (seconds < 10 ? '0' : '') + seconds)

            tmpTimeDisplay = tDateHr + ':' + tDateMin + ':' + tDateSec

            this.setState({ tTimeDisplay: tmpTimeDisplay })
            // this.setState({ tTimeDisplay: tDateHr + ':' + tDateMin + ':' + (tDateSec < 10 ? '0' : '') + tDateSec })
            // this.setState({ tTimeDisplay: 'S:' + parseInt(tDateDiff/1000).toString() })
        }
    }
    componentDidMount() {
        // Should there be a 'clearInterval' prior to setting a new one (same as what is run in 'componentWillUnmount')?
            // Although I know it's not in the instance state anymore (that's been replaced),
            // but is the previous setInterval still counting in memory?
        this.setState({ interval: setInterval(this.tick, 1000) })
    }
    componentWillUnmount() {
        clearInterval(this.state.interval)
    }
    render() {
        return (
            <span className="nowrap">{this.state.tTimeDisplay.length > 0 ? 'T-' + this.state.tTimeDisplay : ''}</span>
        )
    }
}
class Timer extends React.Component {
    constructor(props) {
        super(props)
        this.toggleTimeout = this.toggleTimeout.bind(this)
        this.removeTimer = this.removeTimer.bind(this)
    }
    toggleTimeout() {
        this.props.toggleTimeout(this.props.entry.id, this.props.entry.active ? 'off' : 'on')
    }
    removeTimer() {
        this.props.removeTimer(this.props.entry.id)
    }
    render() {
        const { Glyphicon } = ReactBootstrap

        const timeoutEntry = this.props.timeoutList.find( elem => elem.id === this.props.entry.id),
              timerDisplayEntry = (timeoutEntry) ? this.props.timerDisplayList.find( elem => elem.id === timeoutEntry.timer) : null,
              timeDisplay = (timerDisplayEntry) ? timerDisplayEntry.destination : 0

        return (
            <tr>
                <td>{this.props.entry.title}</td>
                <td>{this.props.entry.time}</td>
                <td>{this.props.entryCycleList[this.props.entry.cycle]}</td>
                <td className="text-center">
                    <input
                        onChange={this.toggleTimeout.bind(this)}
                        type="checkbox"
                        value={this.props.entry.id}
                        checked={this.props.entry.active}
                    />
                </td>
                <td className="text-center">
                    <TimeDisplay targetTime={timeDisplay} />
                </td>
                <td>
                    <button
                        className="btn btn-xs"
                        onClick={this.removeTimer.bind(this)}
                    ><Glyphicon glyph="remove" /></button>
                </td>
            </tr>
        )
    }
}
class SettingsForm extends React.Component {
    constructor(props) {
        super(props)

        this.state = { ...this.initialState() }

        this.updateEntry = this.updateEntry.bind(this)
        this.submitEntry = this.submitEntry.bind(this)
        this.resetState = this.resetState.bind(this)
    }
    initialState() {
        return {
            entryTitle: '',
            entryHours: 0,
            entryMinutes: 0,
            entryCycleSelect: 0, // [daily|hourly|every minute]
            active: false
        }
    }
    resetState() {
        const newObj = Object.assign({}, {...this.state}, {...this.initialState()})
        this.setState( newObj )
    }
    updateEntry(e) {
        const newVal = e.target.dataset.type === 'number' ? parseInt(e.target.value, 10) : e.target.value
        this.setState({ [e.target.name]: newVal })
    }
    submitEntry(e) {
        e.preventDefault()

        if ( typeof(this.state.entryTitle) !== 'string' || this.state.entryTitle.length === 0 ) {
            console.log('A [Title] is required')
        } else if (
                (typeof(this.state.entryHours) !== 'number' || typeof(this.state.entryMinutes) !== 'number') ||
                (this.state.entryHours < 0 || this.state.entryHours >= this.props.countHours) ||
                (this.state.entryMinutes < 0 || this.state.entryMinutes >= this.props.countMinutes)
            ) {
            console.log('Invalid time set: It should be [0-' + (this.props.countHours-1) + ']:[0-' + (this.props.countMinutes-1) + ']')
        } else if (
                (typeof(this.state.entryCycleSelect) !== 'number') ||
                (this.state.entryCycleSelect < 0 || this.state.entryCycleSelect >= this.props.entryCycleList.length)
            ) {
            console.log('Invalid Cycle: It should be one of [' + [...this.props.entryCycleList] + ']')
        } else {

            // FORM DATA IS GOOD

            if (document.referrer !== 'https://codepen.io/KeithDC/' && document.referrer !== 'https://codepen.io/KeithDC') {
                this.textInput.focus()
            }

            // -- Send form data back to parent component callback
            this.props.setTimer(
                this.state.entryTitle,
                this.state.entryHours,
                this.state.entryMinutes,
                this.state.entryCycleSelect)

            // -- Reset local state
            this.resetState()
        }
    }
    formOptions = (idx) => {
            // Pre-ES8 (ES2017)
            // <option value={idx}>{idx.toString().length === 1 ? '0'+idx : idx}</option>
        return (
            <option value={idx}>{idx.toString().padStart(2, '0')}</option>
        )
    }; // Unsure why this semi-colon is required here (at least in codepen)
    render() {
        const setSelectOptions = (maxCount, stepCount=1) => {
                let optionTags = []
                for (let ii = 0; ii<maxCount; ii+=stepCount) {
                    optionTags.push(this.formOptions(ii))
                }
                return optionTags
            },
            setSelectOptionsHours = setSelectOptions(this.props.countHours),
            setSelectOptionsMinutes = setSelectOptions(this.props.countMinutes, this.props.stepCountMinutes),
            timeProps = {
                required: true,
                onChange: this.updateEntry
            },
            countDownChars = (this.props.titleCount - this.state.entryTitle.length)

        return (
            <form onSubmit={this.submitEntry}>
                <h2>Create a Timer</h2>
                <div>
                    <label>Timer Title ({countDownChars} chars)</label>
                    <input
                        type="text"
                        name="entryTitle"
                        {...timeProps}
                        value={this.state.entryTitle}
                        placeholder={this.props.titleTemp}
                        maxLength={this.props.titleCount}
                        size={this.props.titleCount}
                        autoFocus={(document.referrer !== 'https://codepen.io/KeithDC/' && document.referrer !== 'https://codepen.io/KeithDC')}
                        ref={(input) => { this.textInput = input; }}
                    />
                </div>
                <div>
                    <label for="entryTimeHr">Time <small>(hrs/ mins/ cycle)</small></label>
                    <select id="entryTimeHr" name="entryHours" data-type="number" {...timeProps} value={this.state.entryHours} size="1">
                        {setSelectOptionsHours}
                    </select>
                    <label for="entryTimeMin" style={{display: 'none'}}>Minutes</label>
                    <select id="entryTimeMin" name="entryMinutes" data-type="number" {...timeProps} value={this.state.entryMinutes} size="1">
                        {setSelectOptionsMinutes}
                    </select>
                    <label for="entryTimeCycle" style={{display: 'none'}}>Cycle</label>
                    <select id="entryTimeCycle" name="entryCycleSelect" data-type="number" {...timeProps} value={this.state.entryCycle} size="1">
                        { this.props.entryCycleList.map( (cycle, idx) => <option key={idx} value={idx} selected={idx === this.state.entryCycleSelect}>{cycle}</option> ) }
                    </select>
                </div>
                <div className="add-button-div">
                    <div className="label hidden">Add It!!!</div>
                    <button id="entryButton" className="btn">Add Your Timer</button>
                </div>
            </form>
        )
    }
}
class TimerAlertPrompt extends React.Component {

   // export default class TimerAlertPrompt extends React.Component {}
        // https://cdnjs.cloudflare.com/ajax/libs/react/15.5.4/react.min.js
        // https://cdnjs.cloudflare.com/ajax/libs/react/15.5.4/react-dom.min.js
        // https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.24.0/babel.min.js

        // https://cdnjs.cloudflare.com/ajax/libs/react-bootstrap/0.31.0/react-bootstrap.js
        // import { Modal, Button, Glyphicon } from 'react-bootstrap'

    constructor(props) {
        super(props)

        this.timerSnooze = this.timerSnooze.bind(this)
        this.timerReset = this.timerReset.bind(this)
        this.timerDisable = this.timerDisable.bind(this)
    }
    timerSnooze() {
        console.log('[TimerAlertPrompt][timerSnooze] 1:', this.props.modalTimerId)
        this.props.timerSnooze(this.props.modalTimerId)
    }
    timerReset() {
        console.log('[TimerAlertPrompt][timerReset] 1:', this.props.modalTimerId)
        this.props.timerReset(this.props.modalTimerId)
    }
    timerDisable() {
        console.log('[TimerAlertPrompt][timerDisable] 1:', this.props.modalTimerId)
        this.props.timerDisable(this.props.modalTimerId)
    }
    render() {
        // const { show, user, hideDelete, userDelete } = this.props;
        // const Modal = ReactBootstrap.Modal
        // const Button = ReactBootstrap.Modal
        // const Glyphicon = ReactBootstrap.Modal
        // const { Modal, Button, Glyphicon } = 'react-bootstrap'
        const { Modal, Button } = ReactBootstrap
        const { show, modalTitle } = this.props

        // id, time, cycleName
        let timerEntry = this.props.timerList.find( elem => (elem.id === this.props.modalTimerId) ) || {}
        timerEntry.cycleName = this.props.entryCycleList[timerEntry.cycle]

        return (
            <Modal show={show} className="modal-alert">
                <Modal.Body>
                    <Button onClick={this.timerSnooze} bsStyle="warning">Snooze <small><small>({this.props.snoozeTime} min)</small></small></Button>
                        <div className="modal-title">{modalTitle}</div>
                    <Button onClick={this.timerReset} bsStyle="success">Done <small><small>(for now)</small></small></Button>
                        <div className="modal-subtitle">[{timerEntry.time}]&nbsp;[{timerEntry.cycleName}]</div>
                    <Button onClick={this.timerDisable} bsStyle="default">Disable</Button>
                </Modal.Body>
            </Modal>
        )
    }
}

ReactDOM.render(
    <TimerBox />,
    root
)

// HTML
    // <h1>Done (for now) <small><small>(WIP)</small></small></h1>
    // <p>
    //     "Done (for now)" is a 'timer' app, providing options to snooze, reset, disable, and delete timers.
    //     This is my second React app, which I'm hoping will become my first React Native (mobile) app.
    // </p>
    // <div id="root"></div>

// @TODO: Calculate countdown time          [DONE: 2017-07-12: 12:29am (~12 hrs 07-11 + ~8 hrs 07-12)]
    // For both the 'initial timeout creation (createTimeout)',
    // and when setTimeout is up and 'Done (for now)' button is selected (updateTimer (repeat timer)).

// @TODO: Alarm Pop-Over Panel:             [DONE: 2017-07-11/12 (scratched option to allow 'snooze')]

    // ,--------------------------.
    // |    | Done (for now) |    |
    // |    `----------------'    |
    // |        TimerTitle        |
    // |    ,----------------.    |
    // |    |     Disable    |    |
    // '--------------------------'

// @TODO: Add Repeat (after OK)             [DONE: 2017-07-12/13; @TODO: Need more testing]

// @TODO: Add a setTimeout completion queue [DONE: 2017-07-14/15; (@TODO added 2017-07-13)]
    // (for multiple timers completing on the same minute)
    // 1. Add to queue[] (array) when setTimeout time is up;
    // 2. Remove from queue[] when closing modal;
    // 3. Run queueCheck() to see if any others have entered since modal was up.

    // In implementing this feature,
        // Had to redo (fix) the timeout creation and update logic ('getTimeDiff' and 'getTimeDiffUpdate')
        // Created a flowchart of the process flow (75% complete; but enough to show pertinent conditions)
        // Made the form's Title input entry 'character count' (in the label) dynamic:
            // It will yield the difference between the 'config setting' (passed down as a prop) and the field's current length.
        // Reset DOM element focus after timer is added, so 'Add Your Timer' button doesn't stay selected (sets focus to Title).

// @TODO: Add Snooze option:                [DONE: 2017-07-17]
    // Added snooze 'config' setting: Minutes to snooze [1-10]

    // ,------------------------.
    // |   | Snooze (3 min) |   |
    // |   `----------------'   |
    // |       TimerTitle       |
    // |   ,----------------.   |
    // |   | Done (for now) |   |
    // |   `----------------'   |
    // | [00:00] [every minute] |
    // |   ,----------------.   |
    // |   |     Disable    |   |
    // '------------------------'

// @TODO:                                   [DONE: 2017-07-16]
    // Set stepCountMinutes back to : 5
    // Change modal verbiage: Modal Title [00:00]
    // Clean up code
        // (especially the redundant if/then/else with the same values in the 'update' method)

// @TODO: Save to localStorage              [DONE: 2017-07-18]
    // { [timerList], snoozeTime }

    // Load on page load (root component 'componentWillMount()')
        // Will send all 'active' timers through 'addRemoveTimeout()' to create new setTimeouts
        // Any timer in 'snooze' state will be reset to its next default time

    // Update storage when:
        // Figured out pretty quick I only needed to 'setStorageItem' after all relevant 'setState's
            // setTimer, updateTimer, and removeTimer
            // addRemoveTimeout, and updateTimeout
    // Provide option to remove local storage for provided username
        // Why? Just delete your timers; the timerList array (the only stored list) will be empty.

// @TODO: Visual Timeout Countdowns         [DONE: 2017-07-19]
    // To show a countdown of active counters: Will need to set up something generic.
    // This is primarily done. Just need to fix the 'timerDisplayList' for 'snooze' and 'done' action buttons when alerts are up.
    // Fixed. Just missed initializing a variable (forgot to `let` it be)

// @TODO: Functional Flowchart              [DONE: 2017-07-20]
    // Finish flowchart (https://www.draw.io/)
    // Ensure all paths are being covered | cross-check with 'streamlined' file
    // Done: I also cross-checked with a super-streamlined file (which isolated a function being declared twice in the same component - whoops.)
    // Exported in a variety of formats, but delivery will be the same as Guess Right - on the README.md on the open-sourced GitHub page.

// @TODO: Testing                           [Done: 2017-07-22 (Sat)]
    // Definitely need more scientific testing covering a range of scenarios.
    // Need to move code to local dev environment to allow for Testing
    // Setup a base test like with Track Your Cash (just a basic 'does it load' test)

// @TODO: CodePen --> GitHub                [Done: 2017-07-22 (Sat)]
    // @TODO: Convert this Pen to a local file structure
    // @TODO: Commit to GitHub (create full readme)
    // @TODO: Deploy to GitHub Pages via Travis
        // https://kdcinfo.github.io/done-for-now/

// <div>
//   <h3 style="padding-left: 1rem; color: #dd0000;">Temporarily Under Construction <b>until: 2017-07-20 (Thursday)</b></h3>
//   <h5 style="padding-left: 1.25rem; color: #dd0000;">Visual countdowns are near complete. Will fix the 'snooze'/'done' button actions later tonight (Wed)</h5>
// </div>

            
          
!
999px
🕑 One or more of the npm packages you are using needs to be built. You're the first person to ever need it! We're building it right now and your preview will start updating again when it's ready.
Loading ..................

Console