text:
import _ from 'lodash';
import React from 'react/addons';

import formLinkHandlers from 'mixins/formLinkHandlers';

import ListOilsSelector from 'components/listOilsSelector';
import ListOilsRecipe from 'components/listOilsRecipe';
import PropertiesOil from 'components/propertiesOil';
import RecipeBreakdown from 'components/recipeBreakdown';
import RecipeFattyAcids from 'components/recipeFattyAcids';
import RecipeProperties from 'components/recipeProperties';
import RecipeTotals from 'components/recipeTotals';
import TooltipQuestion from 'components/tooltipQuestion';

import BootstrapModalLink from 'components/bootstrapModalLink';
import SelectOils from 'modals/selectOils';

let ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;

export default React.createClass( {
    mixins: [
        React.addons.LinkedStateMixin,
        formLinkHandlers
    ],

    getInitialState() {
        return {
            selectedOil: null
        };
    },

    componentDidMount() {
        SelectOils.setRecipeModel( this.props.recipe );
    },

    render() {
        return (

1 - Liquid or Solid soap recipe?

                                            <input type="radio" name="soap-type" value="naoh" checkedLink={this.radioModel( this.props.recipe, 'soapType', 'naoh' ) } />
                                            Solid Soap - using NaOH (Sodium Hydroxide)

                                            <input type="radio" name="soap-type" value="koh" checkedLink={ this.radioModel( this.props.recipe, 'soapType', 'koh' ) }/>
                                            Liquid Soap - using KOH (Potassium Hydroxide)

                                            <input type="radio" name="soap-type" value="mixed" checkedLink={ this.radioModel( this.props.recipe, 'soapType', 'mixed' ) }/>
                                            Hybrid Soap - using both KOH and NaOH

                                <ReactCSSTransitionGroup  transitionName="fade" transitionAppear={true}>
                                    { this.renderKohPurity() }
                                    { this.renderMixedRatios() }
                                </ReactCSSTransitionGroup>

2 - Select recipe units of measure

                                        <input type="radio" name="uom" value="percent" checkedLink={this.radioModel( this.props.recipe, 'uom', 'percent' )} /> Percentages

                                        <input type="radio" name="uom"  value="gram" checkedLink={this.radioModel( this.props.recipe, 'uom', 'gram' )} /> Grams

                                        <input type="radio" name="uom"  value="kilo" checkedLink={this.radioModel( this.props.recipe, 'uom', 'kilo' )} /> Kilograms

                                        <input type="radio" name="uom"  value="pound" checkedLink={this.radioModel( this.props.recipe, 'uom', 'pound' )} /> Pounds

                                        <input type="radio" name="uom"  value="ounce" checkedLink={this.radioModel( this.props.recipe, 'uom', 'ounce' )} /> Ounces

                                <ReactCSSTransitionGroup  transitionName="fade" >
                                    {this.renderTotalSoapWeight()}
                                </ReactCSSTransitionGroup>

3 - Amount of water in recipe

                                            <input type="radio" name="lyeCalcType" value="ratio" checkedLink={this.radioModel( this.props.recipe, 'lyeCalcType', 'ratio' )} />
                                            <input type="text"
                                                   className="form-control compact-numeric"
                                                   placeholder="%"
                                                   valueLink={ this.linkModel( this.props.recipe, 'waterRatio' ) }
                                                />
                                             % Water as a percent of oils - recommended 33%-38%

                                            <input type="radio" name="lyeCalcType" value="concentration" checkedLink={this.radioModel( this.props.recipe, 'lyeCalcType', 'concentration' )} />
                                            <input type="text"
                                                   className="form-control compact-numeric"
                                                   placeholder="%"
                                                   valueLink={ this.linkModel( this.props.recipe, 'recipeLyeConcentration' ) }
                                                />
                                             % Lye Concentration

                                            <input type="radio" name="lyeCalcType" value="concentration" checkedLink={this.radioModel( this.props.recipe, 'lyeCalcType', 'lyewater' )} />
                                            <input type="text"
                                                   className="form-control super-short-numeric"
                                                   placeholder="water"
                                                   valueLink={ this.linkModel( this.props.recipe, 'lyeWaterWaterRatio' ) }
                                                />
                                              :
                                            <input type="text"
                                                   className="form-control super-short-numeric"
                                                   placeholder="lye"
                                                   valueLink={ this.linkModel( this.props.recipe, 'lyeWaterLyeRatio' ) }
                                                />
                                             Water : Lye Ratio

                                            <input type="text"
                                                   className="form-control compact-numeric"
                                                   placeholder="%"
                                                   valueLink={ this.linkModel( this.props.recipe, 'waterDiscount' ) }
                                                />
                                             % Water Discount

4 - Oil superfat

                                        <input type="text"
                                               className="form-control compact-numeric"
                                               placeholder="%"
                                               valueLink={ this.linkModel( this.props.recipe, 'superFat' ) }
                                            />
                                         % superfat of oils - recommended 5%

                                            <input type="checkbox" name="superfar-after"  checkedLink={this.checkboxModel( this.props.recipe, 'superfatAfter', true )} /> Superfat after cook

                                        <TooltipQuestion
                                            placement="top"
                                            >
                                            The recipe will be calculated with 0% superfat but will show the Frgrance Oil Weight to add to the recipe after cooking.
                                        </TooltipQuestion>

5 - Fragrances

                                            <input type="radio" name="fragranceType" value="ratio" checkedLink={this.radioModel( this.props.recipe, 'fragranceType', 'ratio' )} />
                                            <input type="text"
                                                   className="form-control compact-numeric"
                                                   placeholder="%"
                                                   valueLink={ this.linkModel( this.props.recipe, 'fragrance' ) }
                                                />
                                             % oil weight - recommended 3%

                                            <input type="radio" name="fragranceType" value="ppo" checkedLink={this.radioModel( this.props.recipe, 'fragranceType', 'ppo' )} />
                                            <input type="text"
                                                   className="form-control compact-numeric"
                                                   placeholder={ this.smallUomFragrance() }
                                                   valueLink={ this.linkModel( this.props.recipe, 'fragrancePpo', { round: 2 } ) }
                                                />
                                             { this.smallUomFragrance() }/{ this.largeUomFragrance() }

                                    <button onClick={this.addOil} className="pull-right btn btn-xs btn-default"></button>
                                    <BootstrapModalLink
                                        elementToClick={<button className="btn btn-xs btn-default"></button>}
                                        modal={SelectOils}
                                        large={true}
                                        title="Select Recipe Oils"
                                        />

6 - Select Oils

                            <ListOilsSelector
                                onSelectedOil={this.selectedOil}
                                onAddedOil={this.addOil}
                                />

                    <ReactCSSTransitionGroup  transitionName="zoom" >
                        { this.state.selectedOil &&

                                        <button onClick={this.addOil} className="pull-right btn btn-xs btn-default"></button>

Selected Oil Properties

                                <PropertiesOil
                                    oil={ this.state.selectedOil }
                                    />

                        }
                    </ReactCSSTransitionGroup>

                    <ReactCSSTransitionGroup  transitionName="zoom" >
                        { this.props.recipe.countOils() > 0 &&

Recipe Oils

                                    <ListOilsRecipe
                                        recipe={ this.props.recipe }
                                        />

                        }
                    </ReactCSSTransitionGroup>

                <ReactCSSTransitionGroup  transitionName="zoom" >
                    { this.props.recipe.countWeights() > 0 &&

                        <legend>Recipe</legend>

Recipe Oils

                                    <RecipeBreakdown
                                        recipe={ this.props.recipe }
                                        />

Recipe Totals

                                    <RecipeTotals
                                        recipe={ this.props.recipe }
                                        />

Recipe Properties

                                    <RecipeProperties
                                        recipe={ this.props.recipe }
                                        withRange={true}
                                        showTooltips={true}
                                        />

Fatty Acids %

                                    <RecipeFattyAcids
                                        recipe={ this.props.recipe }
                                        />

                    }
                </ReactCSSTransitionGroup>

        );
    },

    renderKohPurity() {
        if ( this.isKoh() ) {
            return (

                        <input type="text" id="inputHelpBlock" className="form-control short-numeric" valueLink={this.linkModel(this.props.recipe, 'kohPurity')}  /> %
                        KOH Purity - recommended 90%

            );
        }
    },

    renderMixedRatios() {
        if ( this.isMixed() ) {
            return (

                            <input type="text" className="form-control short-numeric" valueLink={this.linkModel(this.props.recipe, 'ratioKoh')}  /> %
                             KOH

                            <input type="text" className="form-control short-numeric" valueLink={this.linkModel(this.props.recipe, 'ratioNaoh')}  /> %
                             NaOH

                            <input type="text" className="form-control short-numeric" valueLink={this.linkModel(this.props.recipe, 'kohPurity')}  /> %
                             KOH Purity - recommended 90%

                    { this.props.recipe.mixedTotalRatios() > 100 &&

                            Lye ratios are over 100%

                    }
                    { this.props.recipe.mixedTotalRatios() < 100 &&

                            Lye ratios should total 100%

                    }

            );
        }
    },

    renderTotalSoapWeight() {
        if ( this.percentMode() ) {
            return (

                            Oils total:
                            <input type="text" className="form-control short-numeric" valueLink={ this.linkModel( this.props.recipe, 'totalWeight', { round: this.props.recipe.roundPlaces() + 1 } ) } />

                                <input type="radio" name="total-uom"  value="gram" checkedLink={this.radioModel( this.props.recipe, 'totalUom', 'gram' )} /> Grams

                                <input type="radio" name="total-uom"  value="kilo" checkedLink={this.radioModel( this.props.recipe, 'totalUom', 'kilo' )} /> Kilograms

                                <input type="radio" name="total-uom"  value="pound" checkedLink={this.radioModel( this.props.recipe, 'totalUom', 'pound' )} /> Pounds

                                <input type="radio" name="total-uom"  value="ounce" checkedLink={this.radioModel( this.props.recipe, 'totalUom', 'ounce' )} /> Ounces

                                <input type="checkbox" name="totals-include-water"  checkedLink={this.checkboxModel( this.props.recipe, 'totalsIncludeWater', true )} /> Adjust oil weights to include water in Oils total

                            <TooltipQuestion
                                placement="top"
                                >
                                Less oil will be used as the lye calculator will adjust total oil weights to include water weight.
                            </TooltipQuestion>

            );
        }
    },

    addOil( e, oil ) {
        let oilToAdd;

        oilToAdd = this.state.selectedOil || _.isObject( oil ) && oil;

        if ( oilToAdd ) {
            this.props.recipe.addOil( oilToAdd );
        }
    },

    selectedOil( oil ) {
        this.setState( {
            selectedOil: oil
        } );
    },

    percentMode() {
        return this.props.recipe.isPercentRecipe();
    },

    isKoh() {
        return this.props.recipe.isKohRecipe();
    },

    isMixed() {
        return this.props.recipe.isMixedRecipe();
    },

    smallUomFragrance() {
        return {
            gram: 'g',
            kilo: 'g',
            pound: 'oz',
            ounce: 'oz'
        }[ this.props.recipe.uomToUse() ];
    },

    largeUomFragrance() {
        return {
            gram: 'kg',
            kilo: 'kg',
            pound: 'lb',
            ounce: 'lb'
        }[ this.props.recipe.uomToUse() ];
    }

} );


text:
import React from 'react';
import config from 'config';

import { TwitterButton, PinterestButton, RedditButton, TwitterCount, PinterestCount, RedditCount } from 'react-social';

import ButtonFBLike from 'components/buttonFBLike';
import ButtonGPlusLike from 'components/buttonGPlusLike';

export default React.createClass( {
    render() {
        let url = config.homeUrl;

        return (

                    <TwitterButton
                        className="btn btn-primary btn-xs hidden-sm"
                        url={ url }>

                    </TwitterButton>

                    <PinterestButton
                        className="btn btn-primary btn-pinterest btn-xs hidden-sm"
                        url={ url }>
                           <PinterestCount url={url}/>
                    </PinterestButton>

                    <ButtonGPlusLike
                        url={ url }
                        layout="standard"
                        />

                    <ButtonFBLike
                        url={ url }
                        layout="button_count"
                        />

        );
    }

} );

repo_name: nazar/soapee-ui

repo_language: JavaScript

mime_type: text/plain


text:
/*global FB*/

import React from 'react/addons';
import when from 'when';

export default React.createClass( {

    mixins: [
        React.addons.PureRenderMixin
    ],

    getDefaultProps() {
        return {
            url: null,
            layout: 'box_count'
        };
    },

    componentDidMount() {
        this.initialiseCommentsWidget();
    },

    componentDidUpdate() {
        this.initialiseCommentsWidget();
    },

    render() {
        return (

        );
    },

    initialiseCommentsWidget() {
        function clear() {
            $( this.getDOMNode() ).find( '.button-fb-like' ).empty();
        }

        function initialize() {
            FB.XFBML.parse( this.getDOMNode() );
        }

        when.try( clear.bind( this ) )
            .then( initialize.bind( this ) );
    }

} );


text:
import React from 'react/addons';
import Reflux from 'reflux';
import cx from 'classnames';

import authStore from 'stores/auth';
import formLinkHandlers from 'mixins/formLinkHandlers';

import ValidateRecipeFormFields from 'services/validateRecipeFormFields';

import BootstrapModalLink from 'components/bootstrapModalLink';
import MarkdownEditor from 'components/markdownEditor';

import SignupOrLoginToSaveRecipe from 'modals/signupOrLoginToSaveRecipe';

export default React.createClass( {

    notes: null,

    mixins: [
        Reflux.connect( authStore, 'user' ),
        React.addons.LinkedStateMixin,
        formLinkHandlers
    ],

    getDefaultProps() {
        return {
            onSave: () => {},
            onSaveAs: () => {},
            onPrint: () => {},
            onPrintPreview: () => {},
            onCancel: () => {}
        };
    },

    getInitialState() {
        return {
            name: '',
            errors: {}
        };
    },

    render() {
        let  nameClasses = cx( 'form-group', {
            'has-error': this.state.errors.name
        } );

        return (

                <form className="form-horizontal" onSubmit={ (e) => e.preventDefault() }>
                    <fieldset>
                        <legend>Save recipe?</legend>

                            <legend>Recipe Name</legend>

                                    <input type="text"
                                           className="form-control"
                                           id="inputRecipeName"
                                           placeholder="Type recipe name"
                                           valueLink={ this.linkModel( this.props.recipe, 'name' ) }
                                        />

                                    { this.state.errors.name &&
                                        { this.state.errors.name[ 0 ]}
                                    }

                            <legend>Recipe Description</legend>
                            <MarkdownEditor
                                className="input-description"
                                valueLink={ this.linkModel( this.props.recipe, 'description' ) }
                                placeholder="Add a short description"
                                />

                            <legend>Recipe Notes / Method </legend>
                            <MarkdownEditor
                                className="input-description"
                                valueLink={ this.linkModel( this.props.recipe, 'notes' ) }
                                placeholder="Describe how the recipe is prepared; mention anything interesting or relevant to this recipe"
                                />

                                <legend>Recipe Visibility</legend>

                                    <input type="radio" name="uom" value="percent" checkedLink={this.radioModel( this.props.recipe, 'visibility', 0 )} /> Private - only visible to you

                                    <input type="radio" name="uom" value="percent" checkedLink={this.radioModel( this.props.recipe, 'visibility', 2 )} /> Friends - also visible to your friends

                                    <input type="radio" name="uom" value="percent" checkedLink={this.radioModel( this.props.recipe, 'visibility', 1 )} /> Public - visible to everyone

                                {this.renderSaveRecipeButton()}
                                { this.props.buttonPrint && <button className="btn btn-primary" onClick={ this.printRecipe }> Print Recipe</button> }
                                { this.props.buttonPrint && <button className="btn btn-primary" onClick={ this.printPreviewRecipe }> Print Preview Recipe</button> }
                                { this.props.buttonCancel && <button className="btn btn-primary" onClick={ this.cancelEditing }> Cancel Editing</button> }
                                { this.props.buttonReset && <button className="btn btn-primary" onClick={ this.resetRecipe }> Reset Recipe</button> }

                    </fieldset>
                </form>

        );
    },

    renderSaveRecipeButton() {
        let nameMissing = !(this.props.recipe.getModelValue( 'name' ));
        let buttons = [];

        if ( authStore.isAuthenticated() ) {
            buttons.push(
                <button
                    className="btn btn-primary"
                    key="btn-save"
                    onClick={ this.saveRecipe }
                    disabled={nameMissing || this.props.buttonDisabledSave }>
                     {this.props.buttonCaptionSave || 'Save Recipe'}
                </button> );

            if ( this.props.recipe.getModelValue( 'id' )
                && authStore.isMyId(this.props.recipe.getModelValue( 'user_id' )  ) ) {
                buttons.push( <button className="btn btn-primary" key="btn-save-as" onClick={ this.saveCopyRecipe } disabled={nameMissing}> Save As Copy</button> );
            }

            return buttons;
        } else {
            return (
                <BootstrapModalLink
                    elementToClick={<button className="btn btn-primary" disabled={nameMissing}> Save Recipe</button>}
                    modal={SignupOrLoginToSaveRecipe}
                    />
            );
        }
    },

    saveRecipe() {
        this.setState( {
            errors: {}
        } );

        this.validateForm( this )
            .then( this.props.onSave )
            .catch( setErrors.bind( this ) );
    },

    saveCopyRecipe() {
        this.setState( {
            errors: {}
        } );

        this.validateForm( this )
            .then( this.props.onSaveAs )
            .catch( setErrors.bind( this ) );
    },

    validateForm() {
        return new ValidateRecipeFormFields( {
            name: this.props.recipe.getModelValue( 'name' )
        } )
            .execute();
    },

    printRecipe() {
        setTimeout( () => {
            this.props.onPrint();
        } );
    },

    printPreviewRecipe() {
        setTimeout( () => {
            this.props.onPrintPreview();
        } );
    },

    cancelEditing() {
        this.props.onCancel();
    },

    resetRecipe() {
        this.props.onReset();
    }

} );

///////////////
///

function setErrors( e ) {
    if ( e.name === 'CheckitError' ) {
        this.setState( {
            errors: e.toJSON()
        } );
    } else if (e.responseJSON.errorType === 'NeedsSocialAccount') {
        this.setState( {
            errors: {
               needsSocialAccount: e.responseJSON.message
            }
        } );
    }
}


text:
import React from 'react';

import ShareThis from 'components/shareThis';

export default React.createClass( {

    shouldComponentUpdate() {
        return false;
    },

    render() {
        return (

Soapee is a Saponification calculator, soap recipe and oils database

                                Find us on Reddit or Facebook.
                                All source code is published on GitHub

                                <ShareThis />

        );
    }

} );


text:
import _ from 'lodash';
import moment from 'moment';
import React from 'react';
import { Link } from 'react-router';

import authStore from 'stores/auth';

import ImageableThumbnails from 'components/imageableThumbnails';
import ImageableCarousel from 'components/imageableCarousel';
import MarkedDisplay from 'components/markedDisplay';
import UserAvatar from 'components/userAvatar';

export default React.createClass( {

    getDefaultProps() {
        return {
            imagesType: 'thumbnails',
            state: 'list'
        };
    },

    render() {
        let recipeJournal = this.props.recipeJournal;
        let recipe = this.props.recipe;

        let paramLink = {
            recipeId: recipe.id,
            journalId: recipeJournal.id
        };

        return (
            <div className={ `recipe-journal-item media ${this.props.state}` }>

                    <UserAvatar
                        user={ recipe.user }
                        />

                            <Link to="userProfile" params={ { id: recipe.user_id } }>{ recipe.user.name }</Link>

                            { moment( recipeJournal.created_at ).fromNow() }

                    <MarkedDisplay
                        content={ recipeJournal.journal }
                        />

                    { this.props.imagesType === 'thumbnails' && _.get( recipeJournal, 'images.length' ) > 0 &&
                        <ImageableThumbnails
                            images={ recipeJournal.images }
                            />
                    }

                    { this.props.imagesType === 'carousel' && _.get( recipeJournal, 'images.length' ) > 0 &&

                                <legend>Journal Photos</legend>

                                    <ImageableCarousel
                                        images={ recipeJournal.images}
                                        />

                    }

                        { this.props.state === 'list' && <Link className="btn btn-primary btn-xs" to="recipe-journal" params={ paramLink }> View</Link> }
                        { authStore.isMyId( recipe.user_id ) && <Link className="btn btn-primary btn-xs" to="recipe-journal-edit" params={ paramLink }> Edit</Link> }

        );
    }

} );


text:
import React from 'react/addons';
import cx from 'classnames';

export default React.createClass( {

    mixins: [
        React.addons.LinkedStateMixin
    ],

    getInitialState() {
        return {
            username: null,
            password: null,
            email: null
        };
    },

    render() {
        let passwordFieldType;

        passwordFieldType = this.props.hidePassword ? 'password' : 'text';

        return (

                <form action={ this.signup }>

                            <input type="text"
                                   className="form-control"
                                   placeholder="Your Username"
                                   valueLink={this.linkState( 'username' )}
                                />
                            { this.props.errors.username &&
                                { this.props.errors.username[ 0 ]}
                            }

                            <input type={ passwordFieldType }
                                   className="form-control"
                                   placeholder="Your Password"
                                   valueLink={this.linkState( 'password' )}
                                />
                            { this.props.errors.password &&
                                { this.props.errors.password[ 0 ]}
                            }

                        { this.props.showEmail &&

                                <input type="text"
                                       className="form-control"
                                       placeholder="Email (optional)"
                                       valueLink={this.linkState( 'email' )}
                                    />

                                { this.props.errors.email &&
                                    { this.props.errors.email[ 0 ]}
                                }

                        }

                        <button className="btn btn-lg btn-primary btn-signup" onClick={this.signup}>{ this.props.buttonCaption }</button>

                </form>

        );
    },

    formClassNames( field ) {
        return cx( 'form-group', {
            'has-error': this.props.errors[ field ],
            'has-success': this.state[ field ] && !(this.props.errors[ field ])
        } );
    },

    signup( e ) {
        e.preventDefault();

        this.props.onButtonClick( {
            username: this.state.username,
            password: this.state.password,
            email: this.state.email
        } );
    }

} );


text:
import React from 'react';

export default React.createClass( {

    componentDidMount() {
        let $this = $( this.getDOMNode() );
        let $hook = $this.find( '.hook' );

        $hook.animatedModal( {
            afterClose: triggerClose.bind( this )
        } )
            .open();

        function triggerClose() {
            this.props.closePortal();
        }
    },

    render() {
        return (

                        { this.props.children }

        );
    }

} );


text:
import React from 'react';
import Reflux from 'reflux';
import cx from 'classnames';
import {Link, State} from 'react-router';

import NavLink from 'components/navLink';
import ButtonUserNotifications from 'components/buttonUserNotifications';

import authStore from 'stores/auth';

export default React.createClass( {

    mixins: [
        State,
        Reflux.connect( authStore, 'user' )
    ],

    render() {
        return (

                        <button type="button" className="navbar-toggle" data-toggle="collapse"
                                data-target=".navbar-collapse">
                            Toggle navigation

                        </button>
                        <Link to="home" className="navbar-brand">Soapee</Link>

                            <NavLink to="calculator">Calculator</NavLink>
                            <NavLink to="feed">Feed</NavLink>
                            <NavLink to="recipes">Recipes</NavLink>
                            <NavLink to="oils">Oils</NavLink>
                            <NavLink to="resources">Resources</NavLink>

                        { !(authStore.isAuthenticated()) && this.renderGuestNavigation()}
                        { authStore.isAuthenticated() && this.renderUserNavigation()}

        );
    },

    renderGuestNavigation() {
        return (

                <NavLink to="login">Login</NavLink>
                <NavLink to="signup">Signup</NavLink>

        );
    },

    renderUserNavigation() {
        return (

                    <ButtonUserNotifications />
                    <button type="button" className="btn btn-profile navbar-btn dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
                         {authStore.userName()}

                    </button>

                        <NavLink to="profile">Profile</NavLink>
                        <NavLink to="my-recipes">My Recipes</NavLink>
                        <NavLink to="my-friend-recipes">My Friend's Recipes</NavLink>
                        <NavLink to="my-comments">My Comments</NavLink>
                        <NavLink to="my-status-updates">My Status Updates</NavLink>
                        <NavLink to="saved-recipes">Saved Recipes</NavLink>

                        <NavLink to="logout">Logout</NavLink>

        );
    },

    activeClassForTo( to ) {
        return cx( { active: this.isActive( to ) } );
    }

} );


text:
/*global gapi*/

import React from 'react/addons';
import when from 'when';

export default React.createClass( {

    mixins: [
        React.addons.PureRenderMixin
    ],

    getDefaultProps() {
        return {
            url: null,
            layout: 'tall' //also standard
        };
    },

    componentDidMount() {
        this.initialiseCommentsWidget();
    },

    componentDidUpdate() {
        this.initialiseCommentsWidget();
    },

    render() {
        return (

        );
    },

    initialiseCommentsWidget() {
        function clear() {
            $( this.getDOMNode() ).find( '.button-gplus-like' ).empty();
        }

        function initialize() {
            gapi.plusone.go( this.getDOMNode() );
        }

        when.try( clear.bind( this ) )
            .then( initialize.bind( this ) );
    }

} );


text:
import moment from 'moment';
import React from 'react';
import { Link } from 'react-router';

import MarkedDisplay from 'components/markedDisplay';
import ImageableThumbnails from 'components/imageableThumbnails';
import UserAvatar from 'components/userAvatar';

export default React.createClass( {

    render() {
        let { feedItem } = this.props;
        let { user } = feedItem.feedable_meta;

        return (

                    <UserAvatar
                        user={ user }
                        />

                            <Link to="userProfile" params={ { id: user.id } }>{ user.name }</Link>

                        { this.renderActionDescription() }

                            { moment( feedItem.created_at ).fromNow() }

                    { this.renderFeedableType() }

        );
    },

    renderActionDescription() {
        let { feedable_meta } = this.props.feedItem;

        let action = {
            status_updates: statusUpdate,
            comments: comment,
            recipes: recipes,
            recipe_journals: recipeJournals,
            users: () =>  joined
        }[ this.props.feedItem.feedable_type ];

        return action();

        function statusUpdate() {
            return (
                 posted a <Link to="status-update" params={ { id: feedable_meta.target.id } }>status update</Link>
            );
        }

        function comment() {
            return (
                 commented on
                    <Link to={feedable_meta.target.targetType} params={ { id: feedable_meta.target.id } }>
                         {feedable_meta.target.name}
                    </Link>

            );
        }

        function recipes() {
            return (
                 {feedable_meta.target.actionType}
                    <Link to="recipe" params={ { id: feedable_meta.target.id } }>
                         {feedable_meta.target.name}
                    </Link>

            );
        }

        function recipeJournals() {
            return (
                 added a Recipe Journal to
                    <Link to="recipe" params={ { id: feedable_meta.target.id } }>
                         {feedable_meta.target.name}
                    </Link>

            );
        }

    },

    renderFeedableType() {
        let { feedable_meta } = this.props.feedItem;
        let renderer = {
            status_updates: renderStatusUpdate.bind( this ),
            comments: renderCommentUpdate.bind( this ),
            recipes: renderRecipe.bind( this ),
            recipe_journals: renderRecipeJournals.bind( this ),
            users: () => {}
        }[ this.props.feedItem.feedable_type ];

        return renderer();

        function renderStatusUpdate() {
            return (

                    <MarkedDisplay
                        content={ feedable_meta.target.update }
                        />

                    <ImageableThumbnails
                        images={ this.props.feedItem.feedable.images }
                        />

            );
        }

        function renderCommentUpdate() {
            return (

                    <MarkedDisplay
                        content={ feedable_meta.target.comment || '' }
                        />

            );
        }

        function renderRecipe() {
            return (

                    <ImageableThumbnails
                        images={ this.props.feedItem.feedable.images }
                        />

            );
        }

        function renderRecipeJournals() {
            return (

                    <MarkedDisplay
                        content={ feedable_meta.target.journal }
                        />

                    <ImageableThumbnails
                        images={ this.props.feedItem.feedable.images }
                        />

            );
        }

    }

} );


text:
import React from 'react';
import Reflux from 'reflux';
import cx from 'classnames';
import { OverlayTrigger, Popover } from 'react-bootstrap';

import userNotificationsStore from 'stores/userNotifications';

import UserNotifications from 'components/userNotifications';

export default React.createClass( {

    mixins: [
        Reflux.connect( userNotificationsStore, 'notifications' )
    ],

    render() {
        let unread = userNotificationsStore.sizeUnread();

        if ( this.state.notifications.length ) {
            return (
                <OverlayTrigger trigger='click'
                                rootClose
                                placement='bottom'
                                overlay={ this.renderNotifications() }
                    >
                    <button type="button"
                            className="button-user-notifications btn btn-profile navbar-btn"
                        >
                        { this.renderBadge( unread ) }
                    </button>
                </OverlayTrigger>
            );
        } else {
            return ;
        }
    },

    renderBadge( unread ) {
        if ( unread ) {
            return { unread };
        }
    },

    renderNotifications() {
        return (
            <Popover>
                <UserNotifications
                    notifications={ this.state.notifications }
                    />
            </Popover>
        );
    }

} );


text:
/*global FB*/

import React from 'react/addons';

export default React.createClass( {

    mixins: [
        React.addons.PureRenderMixin
    ],

    componentDidMount() {
        this.initialiseCommentsWidget();
    },

    componentDidUpdate() {
        this.initialiseCommentsWidget();
    },

    render() {
        return (

        );
    },

    initialiseCommentsWidget() {
        $( this.getDOMNode() ).find( '.fb-comments' ).empty();
        FB.XFBML.parse( $( this.getDOMNode() ).get( 0 ) );
    }

} );


text:
import _ from 'lodash';
import React from 'react';
import { Link } from 'react-router';

import roundFormatted from 'utils/roundFormated';

export default React.createClass( {

    render() {
        let uom = _.capitalize( this.props.recipe.uomToUse() ) + 's';

        return (

                { this.props.recipe.countWeights() > 0 &&

                            Oil
                            %
                            {uom}
                            { this.showGrams() && Grams	 }

                        { this.recipeOilRows() }
                        { this.totalsRow() }

                }

        );
    },

    recipeOilRows() {
        let oilRows = this.props.recipe.recipeOilsWeightsRatios();
        let places = this.props.recipe.roundPlaces();

        return _.map( oilRows,  row => {
            return (
                <tr key={ `recipe-oil-row-${ row.oil.id }` }>

                        { this.oilCell( row.oil ) }

                        { _.round( row.ratio * 100 ) }

                        { roundFormatted( row.weight, places ) }

                    { this.showInGrams( row.weight ) }

            );
        } );
    },

    oilCell( oil ) {
        if ( this.props.withOilLinks ) {
            return <Link to="oil" params={ { id: oil.id } }>{ oil.name }</Link>;
        } else {
            return oil.name;
        }
    },

    totalsRow() {
        let totals = _.reduce( this.props.recipe.recipeOilsWeightsRatios(), ( result, oilRow ) => {
            return _.tap( result, r => {
                r.ratio = result.ratio + oilRow.ratio * 100;
                r.weight = result.weight + Number( oilRow.weight );
            } );
        }, { ratio: 0, weight: 0 } );
        let places = this.props.recipe.roundPlaces();

        return (

                    { _.round( totals.ratio, 1 ) }

                    { roundFormatted( totals.weight, places ) }

                { this.showInGrams( totals.weight ) }

        );
    },

    showGrams() {
        return !(this.props.recipe.isUomGrams());
    },

    showInGrams( weight ) {
        let placesGrams = this.props.recipe.roundPlacesForUom( 'gram' );
        let converted = this.props.recipe.convertWeightToGrams( weight );

        if ( this.showGrams() ) {
            return (

                    { roundFormatted( converted, placesGrams ) }

            );
        }
    }

} );


text:
import React from 'react';
import marked from 'marked';

export default React.createClass( {

    render() {

        return (
            <div className="marked-display"
                dangerouslySetInnerHTML={{
                    __html: this.unsafeContent()
                }}
                 >
            </div>
        );

    },

    unsafeContent() {
        return marked( this.props.content, {
            breaks: true,
            sanitize: true,
            smartypants: true
        } );
    }

} );


text:
import React from 'react/addons';
import Reflux from 'reflux';

import recipeActions from 'actions/recipe';

import authStore from 'stores/auth';

import BootstrapModalLink from 'components/bootstrapModalLink';
import Imageable from 'components/imageable';
import MarkdownEditor from 'components/markdownEditor';

import SignupOrLoginToSaveRecipe from 'modals/signupOrLoginToSaveRecipe';
import ValidateComment from 'services/validateComment';

export default React.createClass( {

    mixins: [
        React.addons.LinkedStateMixin,
        Reflux.connect( authStore, 'user' )
    ],

    getInitialState() {
        return {
            journal: null
        };
    },

    render() {
        return (

                <MarkdownEditor
                    placeholder="Add your journal...."
                    valueLink={ this.linkState( 'journal' ) }
                    rows={ 3 }
                    />

                { this.state.errors &&

                        { this.state.errors }

                }

                    { this.renderAddJournalButton() }

        );
    },

    renderAddJournalButton() {
        let button;
        let caption;

        caption =  Add Recipe Journal;

        if ( authStore.isAuthenticated() ) {
            button = <button className="btn btn-primary" disabled={this.state.saving} onClick={ this.addJournal }>{ caption }</button>;
        } else {
            button = (
                <BootstrapModalLink
                    elementToClick={<button className="btn btn-primary">{ caption }</button>}
                    modal={SignupOrLoginToSaveRecipe}
                    action="add recipe journals"
                    />
            );
        }

        return button;
    },

    addJournal() {

        this.setState( {
            saving: true
        } );

        validate.call( this )
            .with( this )
            .then( addJournal )
            //.then( uploadImages )
            .catch( this.showError );

        function validate() {
            return new ValidateComment( {
                comment: this.state.journal
            } )
                .execute();
        }

        function addJournal() {
            return recipeActions.addRecipeJournal( this.props.recipe, this.state.journal );
        }

        function uploadImages( recipeJournal ) {
            this.startUpload( recipeJournal.id );
        }
    },

    startImageUploadHookFn( startUploadFn ) {
        this.startUpload = startUploadFn;
    },

    clearForm() {
        this.setState( {
            saving: false,
            journal: '',
            errors: null
        } );
        recipeActions.getRecipeJournals( this.props.recipe );
    },

    showError( e ) {
        if ( e.name === 'CheckitError' ) {
            this.setState( {
                errors: e.toJSON()
            } );
        } else  {
            this.setState( {
                errors: 'Ooops. Something went wrong!!1'
            } );
        }
    }

} );


text:
import React from 'react/addons';
import DropzoneComponent from 'react-dropzone-component';

import config from 'config';

export default React.createClass( {

    mixins: [
        React.addons.PureRenderMixin
    ],

    componentDidMount() {
        this.props.startImageUpload( this.startImageUpload );
    },

    render() {
        let dropZoneComponentConfig = {
            allowedFiletypes: [ '.jpg', '.png', '.gif' ],
            showFiletypeIcon: true
        };

        let dropZoneJsConfig = {
            addRemoveLinks: true,
            autoProcessQueue: false,
            parallelUploads: 1,
            dictDefaultMessage: 'Click or Drop files here to upload'
        };

        let eventHandlers = {
            sending: this.sending,
            drop: () => {},
            complete: this.removeFile,
            queuecomplete: this.queuecomplete
        };

        return (
            <DropzoneComponent
                className="imageable"
                action={ `${config.baseUrl}imageable` }
                ref="images"
                config={ dropZoneComponentConfig }
                eventHandlers={ eventHandlers }
                djsConfig={ dropZoneJsConfig }
                />
        );
    },

    startImageUpload( imageableId ) {
        this.imageableId = imageableId;

        if ( this.refs.images.state.files.length ) {
            this.refs.images.dropzone.options.autoProcessQueue = true;
            this.refs.images.dropzone.processQueue();
        } else {
            this.props.OnUploadedCompleted();
        }

    },

    sending( file, xhr, params ) {
        params.append( 'imageable_id', this.imageableId );
        params.append( 'imageable_type', this.props.imageableType );
    },

    removeFile( file ) {
        this.refs.images.dropzone.removeFile( file );
    },

    queuecomplete() {
        this.refs.images.dropzone.options.autoProcessQueue = false;
        this.props.OnUploadedCompleted();
    }

} );


text:
import _ from 'lodash';
import React from 'react/addons';

let ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;

export default React.createClass( {

    render() {
        return (

                    <ReactCSSTransitionGroup  transitionName="fade" component="tbody" >
                        { _.map( this.props.recipe.getModelValue( 'oils' ), this.renderOil )}
                        { this.renderTotalsRow() }
                    </ReactCSSTransitionGroup>

                    { this.renderCompletionMessages() }

        );
    },

    renderOil( oil ) {
        let oilWeight;
        let placeholder;
        let uomCaption;

        oilWeight   = this.props.recipe.getOilWeight( oil );
        placeholder = oilWeight ? '' : this.getPlaceholder();
        uomCaption  = oilWeight ? this.getPlaceholder() : '';

        return (
            <tr key={`recipe-oil-${oil.id}`}>

                    {oil.name}

                    <input type="text"
                           className="form-control short-numeric"
                           placeholder={ placeholder }
                           value={ oilWeight }
                           onChange={ this.changed( oil ) }
                        />
                    { uomCaption }

                    <button className="btn btn-danger btn-xxs"
                            onClick={this.removeOilFromRecipe( oil )}>

                    </button>

        );
    },

    renderTotalsRow() {
        return (
            <tr key={`recipe-oil-totals`}>

                        { parseFloat( this.props.recipe.sumWeights() ).toLocaleString() }
                        { this.getPlaceholder() }

        );
    },

    renderCompletionMessages() {
        if ( this.props.recipe.isPercentRecipe() ) {
            if ( this.props.recipe.sumWeights() !== 100 ) {
                return (

                        Total oils % should be 100%.

                );
            }
        } else if (  !( this.props.recipe.sumWeights() > 0 )  ) {
            return (

                    Total oil weights should be greater than 0.

            );
        }
    },

    changed( oil ) {
        return e => {
            this.props.recipe.setOilWeight( oil, e.target.value );
        };
    },

    getPlaceholder() {
        return {
            percent: '%',
            gram: 'g',
            kilo: 'kg',
            pound: 'lb',
            ounce: 'oz'
        }[ this.props.recipe.getModelValue( 'uom' ) ];
    },

    removeOilFromRecipe( oil ) {
        return () => {
            this.props.recipe.removeOil( oil );
        };
    }

} );


text:
import React from 'react';

export default React.createClass( {

    getDefaultProps() {
        return {
            maxPage: 0,
            nextText: '',
            previousText: '',
            currentPage: 0
        };
    },

    render() {
        var previous = '';
        var next = '';
        var pages = [];
        var window = 11;

        var startIndex = Math.max( this.props.currentPage - 5, 0 );
        var endIndex = Math.min( startIndex + window, this.props.maxPage );

        if ( this.props.currentPage === 0 ) {
            previous = 'disabled';
        }

        if ( this.props.currentPage === (this.props.maxPage - 1) ) {
            next = 'disabled';
        }

        if ( this.props.maxPage >= window && (endIndex - startIndex) <= 10 ) {
            startIndex = endIndex - window;
        }

        for ( let i = startIndex; i < endIndex; i++ ) {
            let selected = this.props.currentPage === i ? 'active' : '';
            pages.push(
{i + 1}
 );
        }

        return (

                { pages.length > 1 &&

«

                            {pages}

»

                }

        );
    },

    changeToPage( page ) {
        return (e) => {
            this.props.setPage( page );
            e.preventDefault();
        };
    },

    next(e) {
        this.props.next();
        e.preventDefault();
    },

    previous(e) {
        this.props.previous();
        e.preventDefault();
    }

} );


text:
import _ from 'lodash';
import React from 'react';

import roundFormatted from 'utils/roundFormated';

export default React.createClass( {

    render() {
        let uom = this.props.recipe.uomToUse() + 's';
        let places = this.props.recipe.roundPlaces();
        let discount = this.props.recipe.waterDiscount() ? `(${this.props.recipe.waterDiscount()}% less)` : '';

        return (

                { this.props.recipe.countWeights() > 0 &&

                        { this.props.recipe.totalsIncludeWater() &&

                                    Oil weight accounts for water

                        }

                                Total Water Weight

                                { this.roundedValue( 'summary.totals.totalWaterWeight', places ) } { uom } { discount }

                            { this.gramsColumn( 'summary.totals.totalWaterWeight' ) }

                        { this.props.recipe.isMixedRecipe() &&
                            [

                                        Total NaOH Weight

                                        { this.roundedValue( 'summary.totals.totalNaoh', places ) } {uom}

                                    { this.gramsColumn( 'summary.totals.totalNaoh' ) }

                                ,

                                        Total KoH Weight

                                        { this.roundedValue( 'summary.totals.totalKoh', places ) } {uom} { `at ${this.props.recipe.getModelValue( 'kohPurity' )}% purity` }

                                    { this.gramsColumn( 'summary.totals.totalKoh' ) }

                                ,

                                        Total Lye Weight

                                        { this.roundedValue( 'summary.totals.totalLye', places ) } {uom}

                                    { this.gramsColumn( 'summary.totals.totalLye' ) }

                            ]
                        }
                        { !(this.props.recipe.isMixedRecipe()) &&

                                    Total {this.props.recipe.soapTypeToLye()} Weight

                                    { this.roundedValue( 'summary.totals.totalLye', places ) } {uom} {this.purityInfo()}

                                { this.gramsColumn( 'summary.totals.totalLye' ) }

                        }

                                Total Oil Weight

                                { this.roundedValue( 'summary.totals.totalOilWeight', places ) } { uom }

                            { this.gramsColumn( 'summary.totals.totalOilWeight' ) }

                                Fragrance Oil Weight

                                { this.roundedValue( 'summary.totals.fragranceWeight', places ) } { uom }

                            { this.gramsColumn( 'summary.totals.fragranceWeight' ) }

                        { this.props.recipe.superfatAfter() &&

                                    Superfat after cook

                                    { this.roundedValue( 'summary.totals.totalSuperfat', places ) } { uom }

                                { this.gramsColumn( 'summary.totals.totalSuperfat' ) }

                        }

                                Total Batch Weight

                                { this.roundedValue( 'summary.totals.totalBatchWeight', places ) } { uom }

                            { this.gramsColumn( 'summary.totals.totalBatchWeight' ) }

                        { !(this.props.recipe.superfatAfter()) &&

                                    Superfat

                                    { this.props.recipe.getModelValue( 'superFat')  }%

                                { this.gramsColumn() }

                        }

                                Lye Concentration

                                { this.roundedValue( 'summary.totals.lyeConcentration', places ) }%

                            { this.gramsColumn() }

                        { this.props.recipe.isMixedRecipe() &&

                                    NaOH / KOH Ratio

                                    { this.props.recipe.getModelValue( 'ratioNaoh')  }% / { this.props.recipe.getModelValue( 'ratioKoh')  }%

                        }

                                Water : Lye Ratio

                                { this.roundedValue( 'summary.totals.waterLyeRatio', 3 ) } : 1

                            { this.gramsColumn() }

                                Saturated : Unsaturated

                                { this.roundedSaturation( 'saturated' ) } : { this.roundedSaturation( 'unsaturated' ) }

                            { this.gramsColumn() }

                }

        );
    },

    roundedValue( key, precision ) {
        return roundFormatted( this.props.recipe.getModelValue( key ), precision );
    },

    roundedSaturation( fatType ) {
        return _.round( this.props.recipe.getModelValue( `summary.saturations.${fatType}` ) );
    },

    purityInfo() {
        if ( this.props.recipe.isKohRecipe() ) {
            return `at ${this.props.recipe.getModelValue( 'kohPurity' )}% purity`;
        }
    },

    gramsColumn( key ) {
        let placesGrams = this.props.recipe.roundPlacesForUom( 'gram' );

        if ( !(this.props.recipe.isUomGrams()) ) {
            if ( key ) {
                return (

                        { this.inGrams( key, placesGrams ) }

                );
            } else {
                return  	;
            }
        }
    },

    inGrams( key, precision ) {
        let weight = this.props.recipe.getModelValue( key );
        let inGrams;

        if ( !(this.props.recipe.isUomGrams()) ) {
            inGrams = roundFormatted( this.props.recipe.convertWeightToGrams( weight ), precision );
            return { inGrams } g;
        }
    }

} );


text:
import _ from 'lodash';
import React from 'react';
import { Link, Navigation } from 'react-router';

import UserAvatar from 'components/userAvatar';
import MarkedDisplay from 'components/markedDisplay';

export default React.createClass( {

    mixins: [
        Navigation
    ],

    render() {
        let recipe = this.props.recipe;
        let properties = _.get(recipe, 'summary.properties');

        return (

                            { this.props.showUser &&
                                <Link to="userProfile" params={ { id: recipe.user.id } }>
                                    <UserAvatar
                                        user={ recipe.user }
                                        showTooltip={ true }
                                        />
                                </Link>
                            }
                            <Link className="recipe-name" to="recipe" params={{id: recipe.id}}>{ recipe.name }</Link>

                        {recipe.settings.soapType}

                            Iodine: { properties && _.round( properties.iodine ) }
                            INS: { properties && _.round( properties.ins ) }

                                { this.renderOrderedBreakdowns() }

                                { this.renderOils() }

                                { this.renderFats() }

                    { recipe.description &&

                                <MarkedDisplay
                                    content={ recipe.description }
                                    />

                    }

        );
    },

    renderOrderedBreakdowns() {
        let properties = this.props.recipe.summary.properties;

        return _( properties )
            .keys()
            .filter( property => !( _.contains( [ 'ins', 'iodine' ], property ) ) )
            .sort()
            .map( property => {
                let value = _.round( properties[ property ] );
                return (
                    <li key={ `breakdown-for-${ property }` }>
                        {_.capitalize( property )}:
                        { _.round( value ) }%

                );
            } )
            .value();
    },

    renderFats() {
        let fats = this.props.recipe.summary.breakdowns;

        return _( fats )
            .keys()
            .sort()
            .map( fat => {
                let value = _.round( fats[ fat ], 1 );
                return (
                    <li key={ `fats-${ fat }` }>
                        {_.capitalize( fat )}:
                        { value }%

                );
            } )
            .value();

    },

    renderOils() {
        function renderOil( oil ) {
            return (
                <li key={ `recipe-oil-${ oil.id }` }>
                    <Link to="oil" params={ { id: oil.id } }>{ oil.name }</Link>

            );
        }

        return  _( this.props.recipe.oils )
            .sortBy( 'name' )
            .map( renderOil )
            .value();
    }

} );


text:
/*global gapi*/

import React from 'react/addons';
import when from 'when';
import load from 'load-script';

import Spinner from 'components/spinner';

let plusOne;

export default React.createClass( {

    mixins: [
        React.addons.PureRenderMixin
    ],

    componentDidMount() {
        this.width = $( 'body .container' ).width();

        this.loadPlusOneSDK()
            .then( this.initialiseCommentsWidget );
    },

    componentDidUpdate() {
        this.loadPlusOneSDK()
            .then( this.initialiseCommentsWidget );
    },

    render() {
        return (
            <div id="google-comments-widget" className="google-comments">
                <Spinner />
            </div>
        );
    },

    loadPlusOneSDK() {
        return when.promise( resolve => {
            if ( typeof plusOne === 'undefined' ) {
                load( 'https://apis.google.com/js/plusone.js', () => {
                    plusOne = gapi.comments;
                    resolve();
                } );
            } else {
                resolve();
            }
        } );
    },

    initialiseCommentsWidget() {
        $( this.getDOMNode() ).find( '#google-comments-widget' ).empty();

        gapi.comments.render( 'google-comments-widget', {
            href: this.props.url,
            width: this.width,
            first_party_property: 'BLOGGER',
            view_type: 'FILTERED_POSTMOD'
        } );
    }

} );


text:
import _ from 'lodash';
import React from 'react';

import TooltipQuestion from 'components/tooltipQuestion';

export default React.createClass( {

    getDefaultProps() {
        return {
            withRange: false
        };
    },

    render() {
        return (

                        Property
                        %
                        { this.props.withRange && Recommended	 }

                        { this.renderOrderedBreakdowns() }

                        { this.renderNonProperties() }

        );
    },

    renderOrderedBreakdowns() {
        let properties = this.props.recipe.getModelValue( 'summary.properties' );

        return _( properties )
            .keys()
            .sort()
            .filter( property => { return !( _.contains( [ 'ins', 'iodine' ], property ) ); } )
            .map( property => {
                let value = _.round( properties[ property ] );

                return (
                    <tr key={ `recipe-oil-prop-${ property }` }>

                            {_.capitalize( property )}
                            {this.props.showTooltips &&
                            <TooltipQuestion
                                placement="top"
                            >
                                {this.tooltipsForProperty(property)}
                            </TooltipQuestion>
                            }

                        {value}
                        { this.props.withRange && { this.rangesForProperty( property ) }	 }

                );
            } )
            .value();
    },

    renderNonProperties() {
        let properties = this.props.recipe.getModelValue( 'summary.properties' );

        if (  _.keys( properties ).length  ) {
            return _.map( [ 'iodine', 'ins' ], property => {
                let value = _.round( properties[ property ] );
                return (
                    <tr key={ `non-prop-${ property }` }>
                        {_.capitalize( property )}
                        {value}
                        { this.props.withRange && { this.rangesForProperty( property ) }	 }

                );
            } );
        }
    },

    rangesForProperty( property ) {
        return {
            bubbly: '14 - 46',
            cleansing: '12 - 22',
            condition: '44 - 69',
            hardness: '29 - 54',
            longevity: '25 - 50',
            stable: '16 - 48',
            iodine: '41 - 70',
            ins: '136 - 165'
        }[ property ];
    },

    tooltipsForProperty( property ) {
        return {
            bubbly: 'This is a measure of how much loose, fluffy lather is produced. A "bubbly" lather is produced quickly by a soap, but doesn\'t last long',
            cleansing: 'It is a measure of how water soluble the soap is -- meaning it is a measure of how easily the soap dissolves in difficult situations such as hard water, cold water, or salt water. The Cleansing number does NOT tell you whether the soap will actually get your skin clean.',
            condition: 'The conditioning value is a measure of the soap\'s ability to soften and soothe the skin. The "anti tight-and-dry" property, so to speak.',
            hardness: 'The Hardness number is a measure of the physical hardness-like-a-rock. It tells you how relatively easy it will be to unmold a particular soap after saponification. It does NOT necessarily tell you how long-lived the soap will be.',
            longevity: 'It is a guideline for how long you soap will last. The higher the number the longer lasting the soap. Too low and the soap doesn\'t last sufficiently long in the shower. Too high and the soap doesn\'t lather as well.',
            stable: 'How long the lather will stay fluffy with big bubbles'
        }[ property ];
    }
} );


text:
import React from 'react';
import { Tooltip, OverlayTrigger } from 'react-bootstrap';

export default React.createClass( {

    getDefaultProps() {
        return {
            placement: 'left'
        };
    },

    render() {
        const tooltip = (
            <Tooltip>{ this.props.children }</Tooltip>
        );

        return (

                <OverlayTrigger placement={ this.props.placement } overlay={ tooltip }>

                </OverlayTrigger>

        );
    }

} );


text:
import _ from 'lodash';
import React from 'react';
import { OverlayTrigger, Tooltip } from 'react-bootstrap';

export default React.createClass( {

    getDefaultProps() {
        return {
            tipPlacement: 'top'
        };
    },

    render() {
        return (

                    { this.props.showTooltip &&
                        <OverlayTrigger placement={ this.props.tipPlacement } overlay={ this.tooltip() }>
                            { this.renderLocal() || this.renderImageUrl() }
                        </OverlayTrigger>
                    }

                    { !(this.props.showTooltip) && (this.renderLocal() || this.renderImageUrl()) }

        );
    },

    renderLocal() {
        let user = this.props.user;

        if ( !( _.get( user, 'image_url' ) ) ) {
            return (

            );
        }
    },

    renderImageUrl() {
        let user = this.props.user;

        if ( _.get( user, 'image_url' ) ) {
            return (

            );
        }
    },

    tooltip() {
        return <Tooltip>{ _.get( this.props.user, 'name' ) }</Tooltip>;
    },

    initToolTip() {
        $( this.getDOMNode() ).find('[data-toggle="tooltip"]').tooltip();
    }

} );


text:
import _ from 'lodash';
import React from 'react';

import LocalAuthenticationForm from 'components/localAuthenticationForm';
import ValidateLoginFields from 'services/validateLoginFields';
import authActions from 'actions/auth';

export default React.createClass( {

    getInitialState() {
        return {
            errors: {}
        };
    },

    getDefaultProps() {
        return {
            onLoggedIn: () => {}
        };
    },

    render() {
        return (

                { this.state.badPassword &&

                        Username and Password do not match

                }
                <LocalAuthenticationForm
                    buttonCaption={ this.props.buttonCaption || 'Log In' }
                    errors={ this.state.errors }
                    hidePassword={ true }
                    onButtonClick={this.login}
                    />

        );
    },

    login( payload ) {
        this.setState( {
            badPassword: false,
            errors: {}
        } );

        validateLogin.call( this, payload )
            .with( this )
            .then( loginLocal )
            .then( this.props.onLoggedIn )
            .catch( setErrors );
    }

} );

//////////////////////

function validateLogin( payload ) {
    return new ValidateLoginFields( {
        username: payload.username,
        password: payload.password
    } )
        .execute();
}

function loginLocal( payload ) {
    return authActions.loginLocal( payload.username, payload.password );
}

function setErrors( e ) {
    if ( e.name === 'CheckitError' ) {
        this.setState( {
            errors: e.toJSON()
        } );
    } else if ( _.get( e.responseJSON, 'errorType' ) === 'BadPasswordError' ) {
        this.setState( {
            badPassword: true
        } );
    } else if ( e.status === 422 ) {
        this.setState( {
            errors: e.responseJSON.fields
        } );
    }
}


text:
import React from 'react/addons';
import Reflux from 'reflux';

import authStore from 'stores/auth';
import ValidateComment from 'services/validateComment';

import MarkdownEditor from 'components/markdownEditor';
import BootstrapModalLink from 'components/bootstrapModalLink';

import SignupOrLoginToSaveRecipe from 'modals/signupOrLoginToSaveRecipe';

export default React.createClass( {

    mixins: [
        React.addons.LinkedStateMixin,
        Reflux.connect( authStore, 'auth' )
    ],

    getDefaultProps() {
        return {
            onNewComment: () => {}
        };
    },

    getInitialState() {
        return {
            comments: null
        };
    },

    render() {
        return (

                <MarkdownEditor
                    placeholder="Add your comments...."
                    valueLink={ this.linkState( 'comments' ) }
                    rows={ 3 }
                    />

                { this.state.errors &&

                        { this.state.errors }

                }

                    { this.renderAddCommentButton() }

        );
    },

    renderAddCommentButton() {
        let button;
        let caption;

        caption =  Add Comment;

        if ( authStore.isAuthenticated() ) {
            button = <button className="btn btn-primary" onClick={ this.addComment }>{ caption }</button>;
        } else {
            button = (
                <BootstrapModalLink
                    elementToClick={<button className="btn btn-primary">{ caption }</button>}
                    modal={SignupOrLoginToSaveRecipe}
                    action="post comments"
                    />
            );
        }

        return button;
    },

    addComment() {

        function validate() {
            return new ValidateComment( {
                comment: this.state.comments
            } )
                .execute();
        }

        function triggerAddComment() {
            this.props.onNewComment( this.state.comments );
        }

        function clear() {
            this.setState( { comments: '' } );
        }

        this.setState( {
            errors: null
        } );

        validate.call( this )
            .with( this )
            .then( triggerAddComment )
            .then( clear )
            .catch( this.showError );
    },

    showError( e ) {
        if ( e.name === 'CheckitError' ) {
            this.setState( {
                errors: e.toJSON()
            } );
        } else  {
            this.setState( {
                errors: 'Ooops. Something went wrong!!1'
            } );
        }
    }

} );


text:
import React from 'react';
import Portal from 'react-portal';

import AnimatedModal from 'components/animatedModal';

export default React.createClass({

    render() {
        return (
            <Portal
                isOpened={this.props.isOpened}
                closeOnEsc={true}
                closeOnOutsideClick={true}
                onClose={this.signalClosed}
                >
                <AnimatedModal>
                    Think of the children
                </AnimatedModal>
            </Portal>
        );
    }

});


text:
import React from 'react';
import marked from 'marked';

import TextArea from 'react-textarea-autosize';

export default React.createClass( {

    componentDidMount() {
        $( this.getDOMNode() ).find( 'textarea' ).markdown( {
            iconlibrary: 'fa',
            fullscreen: {
                enable: false
            },
            resize: 'both',
            hiddenButtons: [ 'cmdCode' ],

            onPreview: this.preview
        } );
    },

    render() {
        return (
            <div className="markdown-editor">
                <TextArea
                    useCacheForDOMMeasurements
                    {...this.props}
                    />
            </div>
        );
    },

    preview( e ) {
        return marked( e.getContent(), {
            sanitize: true,
            smartypants: true
        } );
    }

} );


text:
import _ from 'lodash';
import React from 'react/addons';
import Reflux from 'reflux';
import cx from 'classnames';

import oilsStore from 'stores/oils';

export default React.createClass( {

    mixins: [
        Reflux.connect( oilsStore, 'oils' ),
        React.addons.LinkedStateMixin
    ],

    shouldComponentUpdate( nextProps, nextState ) {
        return !( _.isEqual( this.state, nextState ) );
    },

    render() {
        return (

                    <input type="text" className="form-control filter" placeholder="filter oil..."
                           valueLink={this.linkState('filter')}/>

                        <button className="btn btn-default" onClick={this.clearSearch}></button>

                        { this.renderOils() }

        );
    },

    renderOils() {
        let oils;

        function matchToName( oil ) {
            return oil.name.toLowerCase().indexOf( this.state.filter.toLowerCase() ) !== -1;
        }

        function renderOil( oil ) {
            let klass = cx( 'no-select', {selected: oil.name === this.state.selectedOil} );

            return (
                <tr key={ `list-oil-${oil.id}` }>

                        {oil.name}

            );
        }

        if ( this.state.filter ) {
            oils = _( this.state.oils )
                .filter( matchToName, this );

        } else {
            oils = _( this.state.oils );
        }

        return oils
            .sortBy( 'name' )
            .map( renderOil, this )
            .value();
    },

    selectOil( oil ) {
        return () => {
            this.setState( {
                selectedOil: oil.name
            } );

            if ( this.props.onSelectedOil ) {
                this.props.onSelectedOil( oil );
            }
        };
    },

    addOil( oil ) {
        return e => {
            this.setState( {
                selectedOil: oil.name
            } );

            if ( this.props.onAddedOil ) {
                this.props.onAddedOil( e, oil );
            }
        };
    },

    clearSearch() {
        this.setState( {
            filter: null
        } );
    }

} );


text:
import _ from 'lodash';
import React from 'react';
import { Carousel, CarouselItem } from 'react-bootstrap';

import {imageableUrl} from 'resources/imageable';

export default React.createClass( {

    getInitialState() {
        return {
            index: 0,
            direction: null
        };
    },

    handleSelect(selectedIndex, selectedDirection) {
        this.setState({
            index: selectedIndex,
            direction: selectedDirection
        });
    },

    render() {
        return (
            <Carousel activeIndex={this.state.index} direction={this.state.direction} onSelect={this.handleSelect}>
                { _.map( this.props.images, this.renderCarouselItem ) }
            </Carousel>
        );
    },

    renderCarouselItem( image ) {
        return (
            <CarouselItem>
                <img src={ imageableUrl( image ) } />
            </CarouselItem>
        );
    }

} );


text:
import _ from 'lodash';
import React from 'react';

import authFacebook from 'resources/authFacebook';
import authGoogle from 'resources/authGoogle';

import authActions from 'actions/auth';

export default React.createClass( {

    getInitialState() {
        return {
            disabledButtons: {
                facebook: false,
                google: false
            }
        };
    },

    getDefaultProps() {
        return {
            onAuthenticated: () => {}
        };
    },

    render() {
        let disabled = this.state.disabledButtons;

        return (

                    <button className="btn btn-primary link-signup" onClick={this.googleSignin} {...this.isButtonDisabled('google')}>

                            { !(disabled.google) &&  }

                            { disabled.google && 'Signing in' }
                            { !(disabled.google) && 'Sign in with Google+' }

                    </button>

                    <button className="btn btn-primary link-signup" onClick={this.facebookSignin} {...this.isButtonDisabled('facebook')}>

                            { !(disabled.facebook) &&  }

                            { disabled.facebook && 'Signing in' }
                            { !(disabled.facebook) && 'Sign in with Facebook' }

                    </button>

        );
    },

    facebookSignin() {
        this.disableButton( 'facebook' );

        authFacebook( true )
            .then( this.thirdPartySignup( 'facebook' ) )
            .then( this.props.onAuthenticated )
            .finally( this.enableButton( 'facebook' ) );
    },

    googleSignin() {
        this.disableButton( 'google' );

        authGoogle( true )
            .then( this.thirdPartySignup( 'google' ) )
            .then( this.props.onAuthenticated )
            .finally( this.enableButton( 'google' ) );

    },

    isButtonDisabled( button ) {
        if ( this.state.disabledButtons[ button ]) {
            return {
                disabled: 'disabled'
            };
        }
    },

    disableButton( button ) {
        this.setButtonStateFor( button, true );
    },

    enableButton( button ) {
        return () => {
            this.setButtonStateFor( button, false );
        };
    },

    setButtonStateFor( provider, disabled ) {
        let newState;

        newState = _.merge( this.state,  {
            disabledButtons: {
                [ provider ]: disabled
            }
        } );

        this.setState( newState );
    },

    thirdPartySignup( provider ) {
        return userDetails => {
            return authActions.signupOrLoginThirdParty( provider, userDetails );
        };
    }

} );


text:
import config from 'config';
import {resolve} from 'url';

export default function( url ) {
    return resolve( config.baseUrl, url );
}


text:
import when from 'when';

export function get( url, options = {} ) {
    return ajax( url, 'GET', options );
}

export function post( url, options = {} ) {
    return ajax( url, 'POST', options );
}

export function put( url, options = {} ) {
    return ajax( url, 'PUT', options );
}

export function del( url, options = {} ) {
    return ajax( url, 'DELETE', options );
}

///////////
//// PRIVATE

function ajax( url, verb, options ) {
    let ajaxData;

    ajaxData = verb === 'GET' ? options.params : JSON.stringify( options.params );

    return when(
        $.ajax( {
            url,
            data: ajaxData,
            type: verb,
            dataType: 'json',
            headers: {
                Accept: 'application/json',
                'Content-Type': 'application/json'
            },
            xhrFields: {
                withCredentials: true
            }
        } )
    );
}


text:
import _ from 'lodash';

export default function roundFormatted( weight, places = 0 ) {
    let rounded;

    if ( weight ) {
        rounded = _.round( weight, places );
        return parseFloat( rounded ).toLocaleString();
    } else {
        return 0;
    }

}


text:
import _ from 'lodash';
import React from 'react';
import Reflux from 'reflux';
import Griddle from 'griddle-react';
import cx from 'classnames';
import ga from 'react-ga';

import oilsStore from 'stores/oils';

import GriddlePager from 'components/griddlePager';
import Spinner from 'components/spinner';

let recipeOilIds = [];

let SelectedComponent = React.createClass( {

    getInitialState() {
        return {
            checked: _.contains( recipeOilIds, this.props.rowData.id )
        };
    },

    render() {
        return (
            <input
                type="checkbox"
                checked={this.state.checked}
                onChange={this.handleChange}
                />
        );
    },

    handleChange() {
        if ( this.state.checked ) {
            recipeOilIds = _.without( recipeOilIds, this.props.rowData.id );
        } else {
            recipeOilIds.push( this.props.rowData.id );
        }

        this.setState( {
            checked: !(this.state.checked)
        } );
    }
} );

let SelectOilsModal =  React.createClass( {

    statics: {
        setRecipeModel( recipe ) {
            this.recipe = recipe;
        }
    },

    mixins: [
        Reflux.connect( oilsStore, 'oils' )
    ],

    getInitialState() {
        return {
            gridColumns: 'fats-common'
        };
    },

    componentWillMount() {
        recipeOilIds = _.pluck( SelectOilsModal.recipe.getModelValue( 'oils' ), 'id' );
    },

    componentDidMount() {
        ga.modalview( 'select-oils' );
    },

    render() {
        let data = oilsStore.getFlatOilProperties();

        if ( data.length ) {
            return (

                            {this.renderColumnButtons()}

                                <Griddle
                                    results={data}
                                    tableClassName="table table-striped table-hover table-bordered table-condensed"
                                    columns={ this.getViewColumns() }
                                    columnMetadata={ this.defineSelectedMetadata() }
                                    useGriddleStyles={false}
                                    showFilter={true}
                                    showSettings={true}
                                    initialSort="name"
                                    resultsPerPage="13"
                                    settingsText="Select Columns"
                                    settingsToggleClassName="btn btn-default btn-sm"
                                    useCustomPagerComponent="true"
                                    customPagerComponent={GriddlePager}
                                    isMultipleSelection={true}
                                    />

                        <button type="button" className="btn btn-default" onClick={this.props.closeModal}>Cancel</button>
                        <button type="button" className="btn btn-primary" onClick={this.save}>Add Oils</button>

            );
        } else {
            return <Spinner />;
        }
    },

    save() {
        SelectOilsModal.recipe.setRecipeOilsByIds( recipeOilIds );
        this.props.closeModal();
    },

    renderColumnButtons() {
        let fattyClass = cx( 'btn btn-default btn-sm', { active: this.state.gridColumns === 'fats-common' } );
        let fattyAllClass = cx( 'btn btn-default btn-sm', { active: this.state.gridColumns === 'fats-all' } );
        let propClass = cx( 'btn btn-default btn-sm', { active: this.state.gridColumns === 'properties' } );
        let saturationClass     =  cx( 'btn btn-default btn-sm', { active: this.state.gridColumns === 'saturation' } );

        return (

                    <button className={propClass} onClick={this.switchViewTo('properties')}>Properties</button>
                    <button className={saturationClass} onClick={this.switchViewTo('saturation')}>Saturates</button>
                    <button className={fattyClass} onClick={this.switchViewTo('fats-common')}>Fatty Acids - Common</button>
                    <button className={fattyAllClass} onClick={this.switchViewTo('fats-all')}>Fatty Acids - All</button>

        );
    },

    switchViewTo( view ) {
        return () => {
            this.setState( {
                gridColumns: view
            } );
        };
    },

    getViewColumns() {
        let columns = oilsStore.oilPropertyGroupings();

        _.each( columns, ( groupProperties ) => {
            groupProperties.unshift( 'selected' );
        } );

        return columns[ this.state.gridColumns ];
    },

    defineSelectedMetadata() {
        return [
            {
                columnName: 'selected',
                cssClassName: 'selected',
                visible: false,
                displayName: '',
                customComponent: SelectedComponent
            },
            {
                columnName: 'monoSaturated',
                displayName: 'mono-unsaturated %'
            },
            {
                columnName: 'polySaturated',
                displayName: 'poly-unsaturated %'
            }
        ];
    }

} );

export default SelectOilsModal;


text:
import React from 'react';
import ga from 'react-ga';

import authActions from 'actions/auth';

import LocalLoginForm from 'components/localLoginForm';
import LocalSignupForm from 'components/localSignupForm';
import MediaSigninButtons from 'components/mediaSigninButtons';

export default React.createClass( {

    getDefaultProps() {
        return {
            action: 'Save Recipes'
        };
    },

    componentDidMount() {
        ga.modalview( 'sign-up-or-login-to-save-recipe' );
    },

    render() {
        return (

                                Please Login or Sign up to {this.props.action}

Login

Signup

                                                <LocalLoginForm
                                                    onLoggedIn={this.authenticated}
                                                    />

                                                <LocalSignupForm
                                                    buttonCaption="Signup"
                                                    onSignedUp={ this.authenticated }
                                                    />

OR

                                <MediaSigninButtons
                                    onAuthenticated={this.authenticated}
                                    />

                    <button type="button" className="btn btn-default" onClick={this.props.closeModal}>Cancel</button>

        );
    },

    authenticated() {
        this.props.closeModal();
        authActions.SignedUpToLoggedInToSaveRecipe();
    }

} );


text:
import React from 'react';
import { Link, Navigation, State } from 'react-router';
import Reflux from 'reflux';

import MediaSigninButtons from 'components/mediaSigninButtons';
import LocalLoginForm from 'components/localLoginForm';

import authStore from 'stores/auth';

export default React.createClass( {

    mixins: [
        Navigation,
        State,
        Reflux.connect( authStore, 'auth' )
    ],

    getInitialState() {
        return {
            errors: {}
        };
    },

    componentWillUpdate() {
        let path = this.getQuery().nextPath || 'profile';

        if ( authStore.isAuthenticated() ) {
            this.replaceWith( path );
        }
    },

    render() {
        document.title = 'Soapee - Login';

        return (

Log In

                            <LocalLoginForm />

OR

                                <Link to="forgot" className="btn btn-primary">Forgot Password</Link>

OR

                            <MediaSigninButtons />

        );
    }

} );


text:
import React from 'react';
import DocMeta from 'react-doc-meta';

import GridOil from 'components/gridOil';

export default React.createClass( {

    render() {
        document.title = 'Soapee - Oils';

        return (
            <div id="oils">
                <DocMeta tags={ this.tags() } />
                <GridOil />
            </div>
        );
    },

    tags() {
        let description = `Soapee Oils Database`;

        return [
            {name: 'description', content: description},
            {name: 'twitter:card', content: description},
            {name: 'twitter:title', content: description},
            {property: 'og:title', content: description}
        ];
    }

} );


text:
import _ from 'lodash';
import React from 'react';
import Reflux from 'reflux';
import { State, Link } from 'react-router';
import { Button } from 'react-bootstrap';

import userActions from 'actions/user';
import meActions from 'actions/me';

import userProfileStore from 'stores/userProfile';
import userRecipesStore from 'stores/userRecipes';
import userFriendsStore from 'stores/userFriends';
import authStore from 'stores/auth';

import UserAvatar from 'components/userAvatar';
import MarkedDisplay from 'components/markedDisplay';
import RecipesLinkTable from 'components/recipesLinkTable';
import Spinner from 'components/spinner';

export default React.createClass( {

    statics: {
        willTransitionTo: function ( transition, params ) {
            userActions.getProfile( params.id );
        }
    },

    mixins: [
        State,
        Reflux.connect( userProfileStore, 'user' ),
        Reflux.connect( userRecipesStore, 'recipes' ),
        Reflux.connect( userFriendsStore, 'friends' )
    ],

    getInitialState() {
        return {
            display: {
                addFriend: true
            }
        };
    },

    render() {
        return (

                { this.renderLoading() }
                { this.renderUserProfile() }

        );
    },

    renderUserProfile() {
        let user = this.state.user;

        if ( this.pageIsForUser() ) {
            return (

                    <legend>{ user.name } Public Profile</legend>

                        <UserAvatar
                            user={ user }
                            />

                    { user.about &&

                        <MarkedDisplay
                            content={ user.about }
                            />

                    }

                    { this.renderFriends() }

                    { this.renderUserRecipes() }

            );
        }
    },

    renderFriends() {
        let addFriendButton;
        let willRender;

        function renderFriend( user ) {
            return (
                <Link className="friend-profile-link" to="userProfile" params={ { id: user.id } }>
                    <UserAvatar
                        user={ user }
                        showTooltip={ true }
                        />
                </Link>
            );
        }

        if ( this.state.display.addFriend && this.isNotMe() && userFriendsStore.iAmNotFriendOfUser() ) {
            if ( authStore.isAuthenticated() ) {
                addFriendButton = (
                    <Button bsStyle="primary"
                            onClick={ this.addFriend }
                            className="button-add-friend"
                        >
                         Add Friend
                    </Button>
                );
            }
        }

        willRender = typeof addFriendButton !== 'undefined' || this.state.friends.length > 0;

        return (

                { willRender && <legend>Friends</legend> }
                { addFriendButton }
                { _.map( this.state.friends, renderFriend, this ) }

        );
    },

    renderUserRecipes() {
        if ( this.state.recipes.length ) {
            return (

                    <legend>Public Recipes</legend>
                    <RecipesLinkTable
                        recipes={ this.state.recipes }
                        />

            );
        }
    },

    renderLoading() {
        if ( !(this.pageIsForUser()) ) {
            return <Spinner />;
        }
    },

    pageIsForUser() {
        let requested = Number( this.getParams().id );
        let got = Number( _.get( this.state.user, 'id' ) );

        return requested === got;
    },

    isNotMe() {
        return !(authStore.isMyId( _.get( this.state.user, 'id' ) ));
    },

    addFriend() {
        meActions.addFriend( this.state.user )
            .then( this.hideAddFriend )
            .then( this.notifySent )
            .catch( this.showError );
    },

    hideAddFriend() {
        this.setState( {
            display: {
                addFriend: false
            }
        } );
    },

    notifySent() {
        $.bootstrapGrowl( `Friend Request Sent to ${this.state.user.name}`, { type: 'warning', delay: 5000 } );
    },

    showError( e ) {
        if ( e.status === 422 ) {
            $.bootstrapGrowl( `Friend Request has already been sent to ${this.state.user.name}`, { type: 'danger', delay: 5000 } );
        } else {
            $.bootstrapGrowl( 'Unknown error has occurred. Please contact us on our Facebook Page or sub-Reddit; links are in the home page', { type: 'danger', delay: 5000 } );
        }
    }

} );


text:
import React from 'react';
import DocMeta from 'react-doc-meta';

import { Link } from 'react-router';

import background from 'assets/img/landing-background.jpg';

export default React.createClass( {

    componentDidMount() {
        document.title = 'Soapee';
    },

    render() {
        let style = {
            background: `url(${background}) no-repeat center center scroll`
        };

        return (

                <DocMeta tags={ this.tags() } />

Soapee

Soap Making Community and Resources

                            <button className="btn btn-primary btn-lg" onClick={ this.scrollTo( '#about' ) }>Find Out More</button>

Soapee is a Saponification Calculator and a Soap Recipe Database

<Link to="signup">Register</Link> today to start creating, saving and sharing your soap recipes

                                    Soap Calculator

Make Solid or Liquid soaps.

                                <Link to="calculator" className="btn btn-primary">Start</Link>

                                    Recipe Database

View user submitted recipes

                                <Link to="recipes" className="btn btn-primary">Start</Link>

                                    Oils Database

View oil properties

                                <Link to="oils" className="btn btn-primary">Start</Link>

                                    Resources

View soap making resources

                                <Link to="resources" className="btn btn-primary">Start</Link>

Soapee is a community driven resource. Find us on Reddit and Facebook to add your ideas, suggestions and feedback.

                            All Soapee source code is released to the public domain. The UI and API source
                            code can be found here and
                            here.

        );
    },

    scrollTo( ref ) {
        return () => {
            $( 'html,body' ).animate( {
                scrollTop: $( ref ).offset().top
            }, 1000 );
        };
    },

    tags() {
        let description = 'Soapee is a Saponification Calculator and a Soap Recipe Database';

        return [
            {name: 'description', content: description},
            {name: 'twitter:card', content: description},
            {name: 'twitter:title', content: description},
            {property: 'og:title', content: description}
        ];
    }

} );


text:
import _ from 'lodash';
import moment from 'moment';
import React from 'react/addons';
import Reflux from 'reflux';
import { Link } from 'react-router';

import meActions from 'actions/me';
import statusUpdateActions from 'actions/statusUpdate';

import myStatusUpdatesStore from 'stores/myStatusUpdates';

import ValidateComment from 'services/validateComment';

import Imageable from 'components/imageable';
import ImageableThumbnails from 'components/imageableThumbnails';
import MarkdownEditor from 'components/markdownEditor';
import MarkedDisplay from 'components/markedDisplay';

let ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;

export default React.createClass( {

    mixins: [
        React.addons.LinkedStateMixin,
        Reflux.connect( myStatusUpdatesStore, 'statusUpdates' )
    ],

    getInitialState() {
        return {
            statusUpdate: null
        };
    },

    componentDidMount() {
        meActions.getMyStatusUpdates();
    },

    render() {
        document.title = 'Soapee - My Status Updates';
        return (

                    <legend>Add a Status Update</legend>
                    <MarkdownEditor
                        placeholder="Add your status update...."
                        valueLink={ this.linkState( 'statusUpdate' ) }
                        rows={ 4 }
                        />

                    <legend>Add Photos</legend>
                    <Imageable
                        imageableType='status_updates'
                        startImageUpload={ this.startImageUploadHookFn }
                        OnUploadedCompleted={ this.clearAndReload }
                        />
                    <button className="btn btn-primary"
                            onClick={ this.addStatusUpdate }
                            disabled={ this.state.saving }
                        >
                         Add a Status Update
                    </button>

                { this.state.errors &&

                        { this.state.errors }

                }

                    <legend>My Status Updates</legend>
                    <ReactCSSTransitionGroup  transitionName="zoom">
                        { this.renderStatusUpdates() }
                    </ReactCSSTransitionGroup>

        );
    },

    renderStatusUpdates() {
        return _.map( this.state.statusUpdates, this.renderStatusUpdate );
    },

    renderStatusUpdate( statusUpdate ) {
        return (

                    Posted on {moment(statusUpdate.created_at).format('LLL')}

                <MarkedDisplay
                    content={statusUpdate.update}
                    />

                <ImageableThumbnails
                    images={ statusUpdate.images }
                    />

                    <Link className="btn btn-primary btn-xs" to="status-update" params={ { id: statusUpdate.id } }> View</Link>
                    <Link className="btn btn-primary btn-xs" to="status-update-edit" params={ { id: statusUpdate.id } }> Edit</Link>

        );
    },

    startImageUploadHookFn( fnToStartUploads ) {
        this.startUploads = fnToStartUploads;
    },

    saveCaption() {
        return this.state.saving ? 'Saving Recipe' : 'Save Recipe';
    },

    addStatusUpdate() {
        let newUpdate;

        this.setState( {
            errors: null,
            saving: true
        } );

        validate.call( this )
            .with( this )
            .then( createStatusUpdate )
            .then( data => newUpdate = data )
            .then( uploadImages )
            .catch( this.showError );

        function validate() {
            return new ValidateComment( {
                comment: this.state.statusUpdate
            } ).execute();
        }

        function createStatusUpdate() {
            return statusUpdateActions.addStatusUpdate( this.state.statusUpdate );
        }

        function uploadImages() {
            this.startUploads( newUpdate.id );
        }
    },

    clearAndReload() {
        this.setState( {
            statusUpdate: '',
            saving: false
        } );

        meActions.getMyStatusUpdates();
    },

    showError( e ) {
        if ( e.name === 'CheckitError' ) {
            this.setState( {
                errors: e.toJSON()
            } );
        } else  {
            this.setState( {
                errors: 'Ooops. Something went wrong!!1'
            } );
        }
    }

} );