<repo_name>devqi/react-playground<file_sep>/tasks-mgmt/README.md
# 

## pure react template - v1 
##### Prerequisites: nodejs, npm, yarn are already installed.

##### Create necessary files for the projects:
- **yarn init** or **npm init** => generate package.json which includes basic information, such as name, version, author, license, etc.
- create a folder named **public** and add a file named **index.html** under it.
- create a folder named **src** and add a file named **index.js** under it.
- [optional] set up a web server **live-server** by using **yarn add [global] live-server** or **npm install [-g] live-server**.
- set up Babel locally
    - run **yarn add babel-cli babel-preset-react babel-preset-env** to add these two to the dependency-list in package.json to allow JSX to be complied automatically. Meanwhile, **node_modules** and **yarn.lock** are added. The concrete specification can be found in [Babel Plugins](https://babeljs.io/docs/plugins/). 
    - add a new file **.babelrc** and configre babel presets/plugins in it.
<br>
:exclamation::exclamation::exclamation: Babel has on its own no functionality, which means Babel is a compiler that is not going to complie anything by default. Therefore, various plugins and presets need to be added to reach the compilation goal.
- set up Webpack: run **yarn add webpack** to add webpack locally.
    - add a file **webpack.config.js** under the project root folder and define a simple configuration in this file according to the [configuraiton template](https://webpack.js.org/concepts/configuration/).
    - install **babel-loader** and configure it in the file to handle JSX.
    - [optional] install **webpack-dev-server** and configure it in **webpack.config.js**.
    - add a file **.babelrc** under the project root folder to specify the babel presets and plugins.

<br>
:exclamation::exclamation::exclamation: Webpack is an asset bundler which allows us to bundle all the stuff that makes up applications and the applied 3rd-Party libraries into a single JavaScript.


<file_sep>/README.md
# react-playground

### indecision-app: 
##### a TODOs app 

### expensify-app: 
##### an expense/income management app

<file_sep>/indecision-app/src/components/Header.js
import React from 'react';

// export default class Header extends React.Component {
//     render() {
//         return (
//             <header>
//                 <h1 className='title'>Indecision App</h1>
//                 <h2 className='subtitle'>Put your life in control</h2>
//             </header>
//         ); 
//     }
// }

const Header = () => (
    <header>
        <div className='container'>
            <h1 className='title'>Indecision App</h1>
            <h2 className='subtitle'>Put your life in control</h2>
        </div>
    </header>
);

export default Header;<file_sep>/indecision-app/src/components/Actions.js
import React from 'react';

// export default class OperateOption extends React.Component {

//     render() {
//         const btnsWhenHasOptions = <div className='operation_buttons'>
//             <button className='choose_an_option' onClick={this.props.handleChooseAnOption}>What should I do?</button>
//             <button className='remove_all_options' onClick={this.props.handleRemoveAllOptions}>Remove all</button>
//         </div>;

//         return (
//             this.props.options.length > 0 ? btnsWhenHasOptions : ''
//         );
//     }
// }

const Actions = (props) => {
    return (
        <div className='operation_buttons'>
            <button className='choose_an_option big-button' onClick={props.handleChooseAnOption} disabled={!(props.options.length > 0 )}>What should I do?</button>
        </div>
    );
}

export default Actions;
    <file_sep>/indecision-app/src/components/AddOption.js
import React from 'react';

export default class AddOption extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            error: undefined
        };

        this.handleAddAnOption = this
            .handleAddAnOption
            .bind(this);

    }

    handleAddAnOption(e) {
        e.preventDefault();

        // get the content of <input>
        const option = e.target.elements.option.value.trim();
        // pass 'option' to the function in IndecisionApp.js
        const error = this.props.handleAddAnOption(option);

        this.setState(() => ({ error }));
        
        if (!error) {
            e.target.elements.option.value = '';
        }
    }

    render() {
        return (
            <div>
                {this.state.error && <p className='add-option-error'>{this.state.error}</p>}
                <form className='add-option' onSubmit={this.handleAddAnOption}>                    
                    <input className='add-option__input' type="text" name="option"/>
                    <button className='button'>Add Option</button>
                </form>
            </div>
        );
    }
}<file_sep>/indecision-app/src/components/Option.js
import React from 'react';

// export default class Option extends React.Component {
    
//     render() {
//         return (
//             <li>
//                 {this.props.option}
//                 <button onClick={e => this.props.handleRemoveAnOption(this.props.option) } >remove</button>
//             </li>
//         );
//     }
// }

const Option = (props) => (
    <li className='option'>
        <p className='option__text'>{props.optionIndex}.  {props.option}</p>
        <button className='button--link' onClick={e => props.handleRemoveAnOption(props.option) } >remove</button>
    </li>
);

export default Option;<file_sep>/indecision-app/src/components/Options.js
import React from 'react';
import Option from './Option';
import md5 from 'react-native-md5';

// export default class Options extends React.Component {
//     render() {
//         let options = this.props.options;

//         return (
//             <div>
//                 <ol>
//                     {                        
//                         options.map(option => 
//                             <Option
//                                 key={md5.hex_md5(option + '')}
//                                 option={option}
//                                 handleRemoveAnOption={this.props.handleRemoveAnOption}/>
//                         )
//                     }
//                 </ol>
//             </div>
//         );
//     }
// }

const Options = (props) => (
    <div>
        <div className='widget-header'>
            <h3 className='widget-header__title'>Your Options</h3>
            <button className='remove_all_options button button--link' onClick={props.handleRemoveAllOptions}>Remove all</button>
        </div>
        
        { 
            !(props.options.length > 0) && <p className='widget__message'>Please add an option to get started !</p>
        }
        
        
        <ul className='options'>
            {                        
                props.options.map((option, index) => 
                    <Option
                        key={md5.hex_md5(option + '')}
                        option={option}
                        optionIndex = {index + 1}
                        handleRemoveAnOption={props.handleRemoveAnOption}/>
                )
            }
        </ul>
    </div>
);

export default Options;<file_sep>/my-own-practice/src/test.js
import React from 'react';
import ReactDom from 'react-dom';

const user = {
	'firstname': 'Qi',
	'lastname': 'Zhang'
};

function formatUser(user) {
	return user.lastname + ', ' + user.firstname;
}

class Greeting extends React.Component {
	constructor(props) {
		super(props);
		this.state = {
			date: new Date(),
			isToggleOn: true
		};

		/**
		 * Must be careful about the meaning of this in JSX callbacks. 
		 * In JavaScript, class methods are not bound by default. 
		 * If you forget to bind this.handleClick and pass it to onClick,
		 *  this will be undefined when the function is actually called.
		 * 
		 * This is not React-specific behavior;
		 *  it is a part of "how functions work in JavaScript". 
		 * Generally, if you refer to a method without () after it, 
		 * such as onClick={this.handleClick}, you should bind that method.
		 * https://www.smashingmagazine.com/2014/01/understanding-javascript-function-prototype-bind/
		 */
		// This binding is necessary to make `this` work in the callback
		this.handleClick = this.handleClick.bind(this);
	}

	componentDidMount() {
		this.timerID = setInterval(
			() => this.tick(),
			1000
		);
	}

	componentWillUnmount() {
		clearInterval(this.timerID);
	}

	tick() {
		this.setState({
			date: new Date()
		});
	}

	handleClick() {
		this.setState( prevState => ({
			isToggleOn: !prevState.isToggleOn
		}));
	}

	render(){
		if(this.state.user) {
			return (
				<div>
			        <h1>Hello {formatUser(this.state.user)} !</h1> 
			        <h2 > It is { this.state.date.toLocaleTimeString() }. </h2> 
		        </div>
	        );
		}
		else {
			return (
				<div>
			        <h1>Hello stranger !</h1> 
			        <h2 > It is { new Date().toLocaleTimeString() }. </h2> 
					<button onClick={this.handleClick}>
						{this.state.isToggleOn ? "On" : "Off"}
					</button>
		        </div>
	        );
		}
	}
}

function Clock(props) {
	return (
		<div>
	        <h1>Hello {formatUser(props.user)} !</h1> 
	        <h2 > It is { props.date.toLocaleTimeString() }. </h2> 
        </div>
    );
}

/**
 * It works because in JavaScript, 
 * true && expression always evaluates to expression, 
 * and false && expression always evaluates to false.
 */
function Mailbox(props) {
    const unreadMessages = props.unreadMessages;
    return (
      <div>
        <h1>Hello!</h1>
        {unreadMessages.length > 0 &&
          <h2>
            You have {unreadMessages.length} unread messages.
          </h2>
        }
      </div>
    );
  }
  
const messages = ['React', 'Re: React', 'Re:Re: React'];

function WarningBanner(props) {
	if (!props.warn) {
	  return null;
	}
  
	return (
	  <div className="warning">
		Warning!
	  </div>
	);
  }
  
  class Page extends React.Component {
	constructor(props) {
	  super(props);
	  this.state = {showWarning: true}
	  this.handleToggleClick = this.handleToggleClick.bind(this);
	}
  
	handleToggleClick() {
	  this.setState(prevState => ({
		showWarning: !prevState.showWarning
	  }));
	}
  
	render() {
	  return (
		<div>
		  <WarningBanner warn={this.state.showWarning} />
		  <button onClick={this.handleToggleClick}>
			{this.state.showWarning ? 'Hide' : 'Show'}
		  </button>
		</div>
	  );
	}
  }
  
function App() {
	  return (
		  <div>
			  <Greeting />
			  <Mailbox unreadMessages={messages} />
			  <Page />
		  </div>
	  );
  }

ReactDom.render(
	<App />,
	document.getElementById('root')
);
<file_sep>/expensify-app/src/components/ExpenseListItem.js
import React from 'react';
import { Link } from 'react-router-dom';

/**
 * snippet#1: passed-in object destructuring
 */
const ExpenseListItem = ( {dispatch, id, description, amount, createdAt, note } ) => (
    <div>
        <Link to={`/edit/${id}`}>
            <h3>{description}</h3>
        </Link>
        <p>{amount} - {createdAt}</p>
        <p>{note}</p>        
        <hr />
    </div>
);

/**
 * snippet#2: normal passed-in props
 */
// const ExpenseListItem = (props) => (
//     <div>
//         <h3>{props.description}</h3>
//         <p>{props.amount} - {props.createdAt}</p>
//         <hr />
//     </div>
// );

export default ExpenseListItem;<file_sep>/expensify-app/src/firebase/firebase.js
import * as firebase from 'firebase';

const config = {
    apiKey: "<KEY>",
    authDomain: "expensify-98a02.firebaseapp.com",
    databaseURL: "https://expensify-98a02.firebaseio.com",
    projectId: "expensify-98a02",
    storageBucket: "expensify-98a02.appspot.com",
    messagingSenderId: "321141692729"
};

firebase.initializeApp(config);

const database = firebase.database();

// database.ref('expenses').push({
//     description: 'water bill',
//     amount: 100,
//     createdAt: 200,
//     note: 'water bill for 2017'
// });

// database.ref('expenses').push({
//     description: 'electricity bill',
//     amount: 120,
//     createdAt: 200,
//     note: 'electricity bill for 2017'
// });

// database.ref('expenses').push({
//     description: 'food',
//     amount: 300,
//     createdAt: 200,
//     note: 'costs of food in 2017'
// });

const ref = database.ref('expenses');
ref.once('value').then((snapshot) => {
    const expenses = [];
console.log('snapshot.key = ', snapshot.key);
    snapshot.forEach((childSnapshot) => {
        console.log('child-snapshot.key = ', snapshot.key);
        
        expenses.push({
            id: childSnapshot.key,
            ...childSnapshot.val()
        });
    });

    console.log('expenses: ', expenses);
});

// firebase
//     .database()
//     .ref()
//     .set({
//         name: 'pandaZ',
//         age: 29,
//         isSingle: false,
//         location: {
//             city: 'Hamburg',
//             country: 'Germany'
//         },
//         job: {
//             title: 'Software Developer',
//             company: 'Amazon'
//         }
//     }).then(
//         (data) => {
//             console.log('this is a success handler');
//         }, 
//         (error) => {
//             console.log('this is a failure handler', error);            
//     });

// firebase
//     .database()
//     .ref('age')
//     .set(31);
// firebase
//     .database()
//     .ref('location/city')
//     .set('Frankfurt am Main');

// firebase
//     .database()
//     .ref('attributes/height')
//     .set(165);
// firebase
//     .database()
//     .ref('attributes/weight')
//     .set(45);


/**
 * fetch data from database
 */
// const ref = database.ref();
// ref.once('value')
//     .then(function (snapshot) {
//         const key = snapshot.key;
//         const value = snapshot.val();
//         console.log(key + ' = ' + value);
//     });
// const onValueChange = (snapshot) =>{
    // const key = snapshot.key;
    // const value = snapshot.val();
    // console.log(key + ' : ' + value);

    /**
     * Syntax 1
     */
    // const name = snapshot.child('name').val();
    // const jobTitle = snapshot.child('job/title').val();
    // console.log(name, 'is a ', jobTitle);
    /**
     * Syntax 2
     */
//     const value = snapshot.val();
//     console.log(`${value.name} is a ${value.job.title} at ${value.job.company}.`)
// };

// ref.on('value', onValueChange);

// setTimeout(() => {
//     ref.update({
//         name: 'Batman',
//         'job/title': 'Lead Consultant',
//         'job/company': 'Google'
//     });
// }, 3500);

// setTimeout(() => {
//     ref.off();
// }, 7000);

// setTimeout(() => {
//     ref.update({
//         name: 'Lee',
//         'job/title': 'Senior Consultant'
//     });
// }, 10500);


///////////////////////////////////////////
// database.ref('attributes').set({
//     height: 180,
//     weight: 100
// }).then(
//     (data) => {
//         console.log('atrributes updated');
//     },
//     (error) => {
//         console.log('atrributes failed updating', error);        
//     }
// );

/**
 * remove a property
 * 
 * refIsSingle.set(null) is equivalent to refIsSingle.remove()
 */
// const refIsSingle = database.ref();
// refIsSingle.remove().then(
//     () => {
//         console.log('remove succeeded.');
//     },
//     (error) => {
//         console.log('remove failed.', error);
//     }
// );

/**
 * update some content in database
 */
// database.ref().update({
//     name: 'Catfish',
//     isSingle: null,
//     'location/city': 'Munich'
// });
<file_sep>/expensify-app/src/tests/actions/filters.test.js
import { setTextFilter, sortByAmount, sortByDate, setStartDate, setEndDate } from '../../actions/filters';
import moment from 'moment';

test('set text filter to be "bill"', () => {
    const text = 'bill';
    const action = setTextFilter(text);
    expect(action).toEqual({
        type: 'TEXT_FILTER',
        text: 'bill' 
    });
});

test('set to be "sortByAmount"', () => {
    const action = sortByAmount();
    expect(action).toEqual({
        type: 'SORT_BY_AMOUNT'
    });
});

test('set to be "sortByDate"', () => {
    const action = sortByDate();
    expect(action).toEqual({
        type: 'SORT_BY_DATE'
    });
});

test('setStartDate', () => {
    const start = moment(0);
    const action = setStartDate(start);
    expect(action).toEqual({
        type: 'SET_START_DATE',
        startDate: start
    });
});

test('setEndDate', () => {
    const end = moment(0);
    const action = setEndDate(end);
    expect(action).toEqual({
        type: 'SET_END_DATE',
        endDate: end
    });
});