├── .babelrc ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .jsbeautifyrc ├── .jscsrc ├── .openshift ├── action_hooks │ ├── build │ ├── deploy │ ├── post_deploy │ ├── pre_build │ └── pre_start_nodejs ├── cron │ ├── README.cron │ ├── daily │ │ └── .gitignore │ ├── hourly │ │ └── .gitignore │ ├── minutely │ │ └── .gitignore │ ├── monthly │ │ └── .gitignore │ └── weekly │ │ ├── README │ │ ├── chrono.dat │ │ ├── chronograph │ │ ├── jobs.allow │ │ └── jobs.deny ├── lib │ ├── setup_custom_nodejs_env │ └── utils └── markers │ └── README ├── .travis.yml ├── Guides └── OpenShift_Deployment.md ├── README.md ├── index.js ├── karma.config.js ├── nodemon.json ├── package.json ├── src ├── ConnectedIntlProvider.js ├── actions │ ├── counter.js │ ├── extensionsActions.js │ ├── localeActions.js │ ├── roomsActions.js │ └── userActions.js ├── api.js ├── client.js ├── components │ ├── App.js │ ├── AppBar.js │ ├── Counter.js │ ├── Dashboard.js │ ├── EmptyComponent.js │ ├── Hall.js │ ├── Layout.js │ ├── Locale.js │ ├── Menu.js │ ├── Room.js │ └── auth │ │ ├── Login.js │ │ ├── Logout.js │ │ └── Signup.js ├── config.js ├── containers │ ├── AppContainer.js │ ├── CounterApp.js │ ├── DashboardContainer.js │ ├── HallContainer.js │ ├── LoginPageContainer.js │ └── RoomContainer.js ├── controllers │ ├── functions │ │ ├── rooms.js │ │ └── users.js │ ├── index.js │ ├── rooms.js │ └── users.js ├── extensions │ ├── counterExtension │ │ ├── actions │ │ │ └── index.js │ │ ├── components │ │ │ └── Counter.js │ │ ├── containers │ │ │ └── index.js │ │ ├── index.js │ │ └── reducers │ │ │ └── index.js │ ├── index.js │ ├── myCounterExample │ │ ├── actions │ │ │ └── index.js │ │ ├── components │ │ │ └── MyCounter.js │ │ ├── containers │ │ │ └── index.js │ │ ├── index.js │ │ └── reducers │ │ │ └── index.js │ ├── paypalPayments │ │ ├── actions │ │ │ └── index.js │ │ ├── components │ │ │ ├── CreatePayment.js │ │ │ ├── CreatePayout.js │ │ │ ├── DisplayUserFunds.js │ │ │ └── ExecutePayment.js │ │ ├── containers │ │ │ ├── ApprovedPaymentContainer.js │ │ │ ├── CreatePaymentContainer.js │ │ │ ├── CreatePayoutContainer.js │ │ │ └── index.js │ │ ├── controllers │ │ │ ├── functions │ │ │ │ └── index.js │ │ │ └── index.js │ │ ├── index.js │ │ ├── lang │ │ │ ├── es.js │ │ │ ├── index.js │ │ │ └── it.js │ │ ├── pages │ │ │ ├── approved.js │ │ │ ├── cancel.js │ │ │ ├── create.js │ │ │ ├── index.js │ │ │ └── payout.js │ │ └── reducers │ │ │ └── index.js │ └── styleSwitcher │ │ ├── actions │ │ └── index.js │ │ ├── components │ │ └── index.js │ │ ├── containers │ │ └── index.js │ │ ├── index.js │ │ └── reducers │ │ └── index.js ├── globals.js ├── helpers │ ├── ApiClient.js │ ├── Html.js │ ├── clearExtensionsData.js │ ├── connectData.js │ ├── cookieTools.js │ ├── customRequire.js │ ├── fetchTools.js │ ├── forgeTools.js │ ├── getActiveReducers.js │ ├── getDataDependencies.js │ ├── loadExtensionsData.js │ ├── loadExtensionsPages.js │ ├── makeRouteHooksSafe.js │ └── objectTools.js ├── issueToken.js ├── lang │ ├── es.js │ ├── index.js │ └── it.js ├── middleware │ ├── logger.js │ ├── promiseSimple.js │ └── transitionMiddleware.js ├── pages │ ├── Dashboard.js │ ├── Hall.js │ ├── Home.js │ ├── Index.js │ ├── Login.js │ ├── Register.js │ └── Room.js ├── reducers │ ├── counter.js │ ├── extensionsReducers.js │ ├── index.js │ ├── localeReducer.js │ ├── roomsReducer.js │ └── userReducer.js ├── rooms │ └── index.js ├── routes.js ├── server.js ├── store │ └── configureStore.js └── themes │ ├── ThemeBody.js │ ├── alternative │ ├── AppBar.js │ ├── Button.js │ ├── Card.js │ ├── Tab.js │ ├── TextField.js │ └── index.js │ ├── default │ ├── AppBar.js │ ├── Button.js │ ├── Card.js │ ├── DropDownMenu.js │ ├── Tab.js │ ├── TextField.js │ └── index.js │ └── index.js ├── static └── favicon.ico ├── test ├── components │ ├── App.spec.js │ ├── Home.spec.js │ └── Layout.spec.js └── utils.js ├── tests.bundle.js └── webpack ├── dev.config.js ├── hapiWebpack.js └── prod.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | presets: ['es2015', 'react', 'stage-0'], 3 | plugins: [ 4 | [ 'transform-decorators-legacy' ], 5 | [ 'transform-runtime' ] 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | static/assets/* 3 | TO_BE_REMOVED 4 | coverage 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | src/lang/en.js 3 | static/assets/* 4 | coverage 5 | npm-debug.log 6 | TO_BE_REMOVED -------------------------------------------------------------------------------- /.jsbeautifyrc: -------------------------------------------------------------------------------- 1 | { 2 | "html": { 3 | "allowed_file_extensions": [html", "xml", "svg"], 4 | "brace_style": "collapse", 5 | "end_with_newline": false, 6 | "indent_char": " ", 7 | "indent_handlebars": false, 8 | "indent_inner_html": false, 9 | "indent_scripts": "keep", 10 | "indent_size": 2, 11 | "max_preserve_newlines": 0, 12 | "preserve_newlines": true, 13 | "wrap_line_length": 0 14 | }, 15 | "css": { 16 | "allowed_file_extensions": ["css", "scss", "sass", "less", "styl"], 17 | "end_with_newline": false, 18 | "indent_char": " ",r 19 | "indent_size": 2, 20 | "newline_between_rules": true, 21 | "selector_separator": " ", 22 | "selector_separator_newline": true 23 | }, 24 | "js": { 25 | "allowed_file_extensions": ["js", "json"], 26 | "brace_style": "collapse", 27 | "break_chained_methods": false, 28 | "indent_char": " ", 29 | "indent_level": 0, 30 | "indent_size": 2, 31 | "indent_with_tabs": false, 32 | "keep_array_indentation": false, 33 | "keep_function_indentation": false, 34 | "max_preserve_newlines": 0, 35 | "preserve_newlines": true, 36 | "space_after_anon_function": false, 37 | "space_before_conditional": true, 38 | "space_in_empty_paren": false, 39 | "space_in_paren": false, 40 | "unescape_strings": false, 41 | "wrap_line_length": 0 42 | } 43 | } -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "esprima": "babel-jscs", 4 | "esnext": true, 5 | "predef": ["ACTIONS_AUTH_PATH"] 6 | "requireCurlyBraces": ["else", "for", "while", "do", "try", "catch", "case", "default"], 7 | "requireSpaceAfterKeywords": [ 8 | "if", 9 | "else", 10 | "for", "while", "do", "switch", "return", "try", "catch" 11 | ], 12 | "requireParenthesesAroundIIFE": true, 13 | "requireSpacesInConditionalExpression": true, 14 | 15 | "requireSpacesInFunctionExpression": { 16 | "beforeOpeningCurlyBrace": true 17 | }, 18 | "requireSpacesInFunctionDeclaration": { 19 | "beforeOpeningCurlyBrace": true 20 | }, 21 | "requireSpacesInsideObjectBrackets": { 22 | "allExcept": ["{", "}", "(", ")" ] 23 | }, 24 | "requireSpacesInsideParentheses": false, 25 | "requireSpacesInsideParenthesizedExpression": { 26 | "allExcept": [ "{", "}", "(", ")", "function" ] 27 | }, 28 | "requireSpacesInsideBrackets": false, 29 | "requireSpacesInsideArrayBrackets": all, 30 | "disallowMultipleVarDecl": "exceptUndefined", 31 | "requireBlocksOnNewline": 1, 32 | "disallowPaddingNewlinesInBlocks": true, 33 | "disallowEmptyBlocks": true, 34 | "disallowSpacesInsideObjectBrackets": false, 35 | "disallowSpacesInsideArrayBrackets": false, 36 | "disallowSpacesInsideParentheses": false, 37 | "disallowSpaceAfterObjectKeys": true, 38 | "requireCommaBeforeLineBreak": true, 39 | "disallowSpacesInCallExpression": true, 40 | "requireSpaceBeforeObjectValues": true, 41 | "requireOperatorBeforeLineBreak": [ 42 | "?", 43 | "+", 44 | "-", 45 | "/", 46 | "*", 47 | "=", 48 | "==", 49 | "===", 50 | "!=", 51 | "!==", 52 | ">", 53 | ">=", 54 | "<", 55 | "<=", 56 | "||", 57 | "&&" 58 | ], 59 | "disallowSpaceAfterPrefixUnaryOperators": ["++", "--", "+", "-", "~", "!"], 60 | "disallowSpaceBeforePostfixUnaryOperators": ["++", "--"], 61 | "requireSpaceBeforeBinaryOperators": [ 62 | "?", "+", "-", "/", "*", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<=" 63 | ], 64 | "requireSpaceAfterBinaryOperators": [ 65 | "?", "+", "/", "*", ":", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<=" 66 | ], 67 | "disallowSpaceAfterBinaryOperators": ["!"], 68 | "disallowImplicitTypeConversion": ["numeric", "boolean", "binary", "string"], 69 | "requireCamelCaseOrUpperCaseIdentifiers": "ignoreProperties", 70 | "disallowKeywords": ["with"], 71 | "disallowMultipleLineStrings": true, 72 | "disallowMultipleLineBreaks": true, 73 | "validateLineBreaks": false, 74 | "validateQuoteMarks": null, 75 | "validateIndentation": 2, 76 | "disallowMixedSpacesAndTabs": true, 77 | "disallowTrailingWhitespace": true, 78 | "disallowTrailingComma": true, 79 | "disallowKeywordsOnNewLine": ["else", "catch"], 80 | "requireLineFeedAtFileEnd": true, 81 | "maximumLineLength": { 82 | "value": 150, 83 | "allowUrlComments": true 84 | }, 85 | "requireCapitalizedConstructors": true, 86 | "requireDotNotation": true, 87 | "requireSpaceAfterLineComment": true, 88 | "validateParameterSeparator": ", ", 89 | "disallowNewlineBeforeBlockStatements": true, 90 | "requirePaddingNewlinesBeforeKeywords": [ 91 | "do", 92 | "for", 93 | "if", 94 | "switch", 95 | "try", 96 | "void", 97 | "while", 98 | "with", 99 | "return" 100 | ], 101 | "excludeFiles": ["node_modules/**", "coverage", "static/assets", "webpack/**", "TO_BE_REMOVED"] 102 | } 103 | -------------------------------------------------------------------------------- /.openshift/action_hooks/build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This is a simple build script and will be executed on your CI system if 3 | # available. Otherwise it will execute while your application is stopped 4 | # before the deploy step. This script gets executed directly, so it 5 | # could be python, php, ruby, etc. 6 | 7 | 8 | # Source utility functions. 9 | source "$OPENSHIFT_REPO_DIR/.openshift/lib/utils" 10 | 11 | # Setup path to include the custom Node[.js] version. 12 | _SHOW_SETUP_PATH_MESSAGES="true" setup_path_for_custom_node_version 13 | 14 | 15 | # we moved the package.json file out of the way in pre_build, 16 | # so that the OpenShift git post-receive hook doesn't try and use the 17 | # old npm version to install the dependencies. Move it back in. 18 | tmp_package_json="$(get_node_tmp_dir)/package.json" 19 | if [ -f "$tmp_package_json" ]; then 20 | # Only overlay it if there is no current package.json file. 21 | [ -f "${OPENSHIFT_REPO_DIR}/package.json" ] || \ 22 | mv "$tmp_package_json" "${OPENSHIFT_REPO_DIR}/package.json" 23 | fi 24 | 25 | 26 | # Do npm install with the new npm binary. 27 | if [ -f "${OPENSHIFT_REPO_DIR}"/package.json ]; then 28 | echo " - Installing dependencies w/ new version of npm ... " 29 | echo 30 | (cd "${OPENSHIFT_REPO_DIR}"; export TMPDIR="/tmp"; npm install -d) 31 | fi 32 | 33 | -------------------------------------------------------------------------------- /.openshift/action_hooks/deploy: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This deploy hook gets executed after dependencies are resolved and the 3 | # build hook has been run but before the application has been started back 4 | # up again. This script gets executed directly, so it could be python, php, 5 | # ruby, etc. 6 | 7 | 8 | # Source utility functions. 9 | source "$OPENSHIFT_REPO_DIR/.openshift/lib/utils" 10 | 11 | 12 | # On slave/serving gears, need to do the install as part of deploy 13 | # so check if its needed. Just ensure the custom Node[.js] version is 14 | # installed. 15 | ensure_node_is_installed 16 | 17 | -------------------------------------------------------------------------------- /.openshift/action_hooks/post_deploy: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This is a simple post deploy hook executed after your application 3 | # is deployed and started. This script gets executed directly, so 4 | # it could be python, php, ruby, etc. 5 | -------------------------------------------------------------------------------- /.openshift/action_hooks/pre_build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This is a simple script and will be executed on your CI system if 3 | # available. Otherwise it will execute while your application is stopped 4 | # before the build step. This script gets executed directly, so it 5 | # could be python, php, ruby, etc. 6 | 7 | 8 | # Source utility functions. 9 | source "$OPENSHIFT_REPO_DIR/.openshift/lib/utils" 10 | 11 | # Ensure custom node version if not installed. 12 | echo "" 13 | ensure_node_is_installed 14 | 15 | 16 | # We need to move the package.json file out of the way in pre_build, so 17 | # that the OpenShift git post-receive hook doesn't try and use the old 18 | # npm version to install the dependencies. 19 | mv "${OPENSHIFT_REPO_DIR}/package.json" "$(get_node_tmp_dir)" 20 | 21 | -------------------------------------------------------------------------------- /.openshift/action_hooks/pre_start_nodejs: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # The pre_start_cartridge and pre_stop_cartridge hooks are *SOURCED* 4 | # immediately before (re)starting or stopping the specified cartridge. 5 | # They are able to make any desired environment variable changes as 6 | # well as other adjustments to the application environment. 7 | 8 | # The post_start_cartridge and post_stop_cartridge hooks are executed 9 | # immediately after (re)starting or stopping the specified cartridge. 10 | 11 | # Exercise caution when adding commands to these hooks. They can 12 | # prevent your application from stopping cleanly or starting at all. 13 | # Application start and stop is subject to different timeouts 14 | # throughout the system. 15 | 16 | 17 | # Source utility functions. 18 | source "$OPENSHIFT_REPO_DIR/.openshift/lib/utils" 19 | 20 | # Setup path to include the custom Node[.js] version. 21 | ver=$(get_node_version) 22 | echo "" 23 | echo " - pre_start_nodejs: Adding Node.js version $ver binaries to path" 24 | _SHOW_SETUP_PATH_MESSAGES="true" setup_path_for_custom_node_version 25 | -------------------------------------------------------------------------------- /.openshift/cron/README.cron: -------------------------------------------------------------------------------- 1 | Run scripts or jobs on a periodic basis 2 | ======================================= 3 | Any scripts or jobs added to the minutely, hourly, daily, weekly or monthly 4 | directories will be run on a scheduled basis (frequency is as indicated by the 5 | name of the directory) using run-parts. 6 | 7 | run-parts ignores any files that are hidden or dotfiles (.*) or backup 8 | files (*~ or *,) or named *.{rpmsave,rpmorig,rpmnew,swp,cfsaved} 9 | 10 | The presence of two specially named files jobs.deny and jobs.allow controls 11 | how run-parts executes your scripts/jobs. 12 | jobs.deny ===> Prevents specific scripts or jobs from being executed. 13 | jobs.allow ===> Only execute the named scripts or jobs (all other/non-named 14 | scripts that exist in this directory are ignored). 15 | 16 | The principles of jobs.deny and jobs.allow are the same as those of cron.deny 17 | and cron.allow and are described in detail at: 18 | http://docs.redhat.com/docs/en-US/Red_Hat_Enterprise_Linux/6/html/Deployment_Guide/ch-Automating_System_Tasks.html#s2-autotasks-cron-access 19 | 20 | See: man crontab or above link for more details and see the the weekly/ 21 | directory for an example. 22 | 23 | -------------------------------------------------------------------------------- /.openshift/cron/daily/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dindaleon/hapi-react-starter-kit/7d0d259268a1f08efd9acb7d3c5dcc362ad4b606/.openshift/cron/daily/.gitignore -------------------------------------------------------------------------------- /.openshift/cron/hourly/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dindaleon/hapi-react-starter-kit/7d0d259268a1f08efd9acb7d3c5dcc362ad4b606/.openshift/cron/hourly/.gitignore -------------------------------------------------------------------------------- /.openshift/cron/minutely/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dindaleon/hapi-react-starter-kit/7d0d259268a1f08efd9acb7d3c5dcc362ad4b606/.openshift/cron/minutely/.gitignore -------------------------------------------------------------------------------- /.openshift/cron/monthly/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dindaleon/hapi-react-starter-kit/7d0d259268a1f08efd9acb7d3c5dcc362ad4b606/.openshift/cron/monthly/.gitignore -------------------------------------------------------------------------------- /.openshift/cron/weekly/README: -------------------------------------------------------------------------------- 1 | Run scripts or jobs on a weekly basis 2 | ===================================== 3 | Any scripts or jobs added to this directory will be run on a scheduled basis 4 | (weekly) using run-parts. 5 | 6 | run-parts ignores any files that are hidden or dotfiles (.*) or backup 7 | files (*~ or *,) or named *.{rpmsave,rpmorig,rpmnew,swp,cfsaved} and handles 8 | the files named jobs.deny and jobs.allow specially. 9 | 10 | In this specific example, the chronograph script is the only script or job file 11 | executed on a weekly basis (due to white-listing it in jobs.allow). And the 12 | README and chrono.dat file are ignored either as a result of being black-listed 13 | in jobs.deny or because they are NOT white-listed in the jobs.allow file. 14 | 15 | For more details, please see ../README.cron file. 16 | 17 | -------------------------------------------------------------------------------- /.openshift/cron/weekly/chrono.dat: -------------------------------------------------------------------------------- 1 | Time And Relative D...n In Execution (Open)Shift! 2 | -------------------------------------------------------------------------------- /.openshift/cron/weekly/chronograph: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "`date`: `cat $(dirname \"$0\")/chrono.dat`" 4 | -------------------------------------------------------------------------------- /.openshift/cron/weekly/jobs.allow: -------------------------------------------------------------------------------- 1 | # 2 | # Script or job files listed in here (one entry per line) will be 3 | # executed on a weekly-basis. 4 | # 5 | # Example: The chronograph script will be executed weekly but the README 6 | # and chrono.dat files in this directory will be ignored. 7 | # 8 | # The README file is actually ignored due to the entry in the 9 | # jobs.deny which is checked before jobs.allow (this file). 10 | # 11 | chronograph 12 | 13 | -------------------------------------------------------------------------------- /.openshift/cron/weekly/jobs.deny: -------------------------------------------------------------------------------- 1 | # 2 | # Any script or job files listed in here (one entry per line) will NOT be 3 | # executed (read as ignored by run-parts). 4 | # 5 | 6 | README 7 | 8 | -------------------------------------------------------------------------------- /.openshift/lib/setup_custom_nodejs_env: -------------------------------------------------------------------------------- 1 | # Utility functions for bash session - sourced in via the user's 2 | # bash profile ($OPENSHIFT_DATA_DIR/.bash_profile). 3 | 4 | # Source utility functions. 5 | source $OPENSHIFT_REPO_DIR/.openshift/lib/utils 6 | 7 | 8 | # Internal function to setup path and remove the wrappers. 9 | function _setup_path_and_remove_wrappers() { 10 | # First invocation of npm or node, so setup the custom path and 11 | # unset the wrappers. Add the custom node binaries to the PATH. 12 | [ -z "$ZDEBUG" ] || echo "Setting path to include custom Node version" 13 | setup_path_for_custom_node_version 14 | unset node 15 | unset npm 16 | unset _setup_path_and_remove_wrappers 17 | 18 | } # End of function _setup_path_and_remove_wrappers. 19 | 20 | 21 | # Temporary wrapper function to setup path before invoking npm. 22 | function npm() { 23 | # Setup path, remove wrappers and reinvoke npm. 24 | _setup_path_and_remove_wrappers 25 | npm "$@" 26 | 27 | } # End of function npm. 28 | 29 | 30 | # Temporary wrapper function to setup path before invoking node. 31 | function node() { 32 | # Setup path, remove wrappers and reinvoke node. 33 | _setup_path_and_remove_wrappers 34 | node "$@" 35 | 36 | } # End of function node. 37 | 38 | 39 | # 40 | # EOF 41 | -------------------------------------------------------------------------------- /.openshift/lib/utils: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Utility functions. 4 | # 5 | 6 | 7 | # Returns the configured Node version 8 | function get_node_version() { 9 | # read the app's package.json file 10 | package_json="${OPENSHIFT_REPO_DIR}/package.json" 11 | if [ ! -f "$package_json" ] ; then 12 | package_json="$(get_node_tmp_dir)/package.json" 13 | fi 14 | 15 | # attempt to detect the desired node.js version 16 | engine=$(grep "\"node\"" "$package_json" | tail -n 1 | sed -e 's/^.*" *[~=<>]*[=<>]* *v*\([0-9][^" ]*\) *[^"]*".*$/\1/p;d') 17 | 18 | # assume that a deprecated marker value is a reasonable default 19 | marker="${OPENSHIFT_REPO_DIR}.openshift/markers/NODEJS_VERSION" 20 | marker_ver=$(egrep -v "^\s*#.*" "$marker" 2>/dev/null | egrep -v "^\s*$" | tail -1) 21 | 22 | # use the OpenShift cart default of 0.6.20 as a final option: 23 | if [ -z $engine ]; then 24 | echo ${marker_ver-"0.6.20"} 25 | else 26 | echo ${engine} 27 | fi 28 | 29 | } # End of function get_node_version. 30 | 31 | 32 | # Returns the directory where Node is to be installed/is installed. 33 | function get_node_install_dir() { 34 | echo "$OPENSHIFT_DATA_DIR" 35 | 36 | } # End of function get_node_install_dir. 37 | 38 | 39 | # Returns the path to the npm binary. 40 | function get_npm_bin_path() { 41 | ver=${1:-"$(get_node_version)"} 42 | echo "$(get_node_install_dir)node-v$ver-linux-x64/bin" 43 | 44 | } # End of function get_npm_bin_path. 45 | 46 | 47 | # Returns the path to the node binary. 48 | function get_node_bin_path() { 49 | echo "$(get_npm_bin_path $@)" 50 | 51 | } # End of function get_node_bin_path. 52 | 53 | 54 | # Returns the temporary directory we use for processing. 55 | function get_node_tmp_dir() { 56 | ztmpdir="$OPENSHIFT_DATA_DIR/.nodejs.tmp" 57 | 58 | # Ensure temp directory is created. 59 | [ -d "$ztmpdir" ] || mkdir -p "$ztmpdir" 60 | 61 | echo "$ztmpdir" 62 | 63 | } # End of function get_node_tmp_dir. 64 | 65 | 66 | # 67 | # Download and install the specified Node.js version. 68 | # 69 | function _install_nodejs() { 70 | ver=${1:-"$(get_node_version)"} 71 | 72 | # Sample download links: 73 | # http://nodejs.org/dist/v0.8.9/node-v0.8.9-linux-x64.tar.gz 74 | # http://nodejs.org/dist/v0.9.1/node-v0.9.1-linux-x64.tar.gz 75 | zfile="node-v${ver}-linux-x64.tar.gz" 76 | zlink="http://nodejs.org/dist/v${ver}/${zfile}" 77 | 78 | instdir="$(get_node_install_dir)" 79 | 80 | # Download and extract the gzipped tarball. 81 | dldir="$OPENSHIFT_DATA_DIR/downloads" 82 | mkdir -p "$dldir" 83 | 84 | echo " - Downloading and extracting $zlink ... " 85 | 86 | if ! curl -L -o "$dldir/$zfile" "$zlink"; then 87 | echo " - ERROR -- download failed for $zlink" 88 | echo " - download uri = $dldir/$zfile" 89 | return 1 90 | fi 91 | 92 | (cd "$instdir"; tar -zxf "$dldir/$zfile") 93 | echo " - Done installing Node.js version $ver" 94 | 95 | } # End of function _install_nodejs. 96 | 97 | 98 | 99 | # Ensure the shell env setup bits are added to user's .bash_profile. 100 | function _ensure_bash_profile_setup() { 101 | dot_bash_profile=$OPENSHIFT_DATA_DIR/.bash_profile 102 | pattern='\s*source(.*)\.openshift/lib/setup_custom_nodejs_env\s*(.*)\s*' 103 | if ! egrep "$pattern" $dot_bash_profile > /dev/null 2>&1 ; then 104 | 105 | cat >> $dot_bash_profile <= = -a App_Name` 41 | 42 | `$ rhc env set NODE_ENV=production -a App_Name` 43 | 44 | `$ rhc env set BABEL_CACHE_PATH=$OPENSHIFT_DATA_DIR -a App_Name` 45 | 46 | 47 | 6. Install Redis cartridge: 48 | 49 | `$ rhc add-cartridge http://cartreflect-claytondev.rhcloud.com/reflect?github=Dindaleon/openshift-redis-cart-1 -a App_Name` 50 | 51 | 7. SSH into your app and install the following dev dependencies required for building the app: 52 | 53 | `npm install ---save-dev rimraf webpack extract-text-webpack-plugin stats-webpack-plugin strip-loader` 54 | 55 | then 56 | 57 | `npm run build` 58 | 59 | 8. Restart your app 60 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-var, vars-on-top, no-console */ 2 | 3 | // Register babel to have ES6 support on the server 4 | require( 'babel-core/register' )({ 5 | ignore: /node_modules/, 6 | presets: [ 'es2015', 'stage-0', 'react' ], 7 | plugins: [ 'transform-decorators-legacy', 'transform-runtime' ] 8 | }); 9 | const chalk = require( 'chalk' ); 10 | require('./src/globals'); 11 | global.__SERVER__ = true; 12 | 13 | const enviroment = __DEVELOPMENT__ ? 'Development' : 'Production'; 14 | 15 | const server = require( './src/server' ).default; 16 | server( server => { 17 | for ( var key of Object.keys(server.connections) ) { 18 | console.info( chalk.bold.green( '==> 🌎 Hapi ' + enviroment + ' Server (' + server.connections[key].name + ') is listening on', server.connections[key].info.uri )); 19 | } 20 | }); 21 | -------------------------------------------------------------------------------- /karma.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* jscs: disable */ 3 | var webpack = require('webpack'); 4 | // Karma configuration 5 | module.exports = function(config) { 6 | config.set({ 7 | 8 | // base path that will be used to resolve all patterns (eg. files, exclude) 9 | basePath: '', 10 | 11 | client: { 12 | mocha: { 13 | reporter: 'spec', 14 | ui: 'bdd' 15 | } 16 | }, 17 | 18 | // frameworks to use 19 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 20 | frameworks: [ 'mocha', 'sinon-chai'], 21 | 22 | // list of files / patterns to load in the browser 23 | files: [ 'tests.bundle.js' ], 24 | 25 | // list of plugins 26 | plugins: [ 27 | 'karma-firefox-launcher', 28 | 'karma-coverage', 29 | 'karma-mocha', 30 | 'karma-mocha-reporter', 31 | 'karma-sinon-chai', 32 | 'karma-sourcemap-loader', 33 | 'karma-webpack' 34 | ], 35 | 36 | // preprocess matching files before serving them to the browser 37 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 38 | preprocessors: { 39 | 'tests.bundle.js': [ 'webpack', 'sourcemap' ] 40 | }, 41 | 42 | reporters: ['mocha', 'coverage'], 43 | 44 | webpack: { 45 | devtool: 'inline-source-map', 46 | plugins: [ 47 | new webpack.DefinePlugin({ 48 | 'process.env.NODE_ENV': JSON.stringify('production'), 49 | '__SERVER__': false, 50 | '__CLIENT__': true 51 | }) 52 | ], 53 | module: { 54 | preLoaders: [ { //delays coverage til after tests are run, fixing transpiled source coverage error 55 | test: /\.js$/, 56 | exclude: [/node_modules/, /test/, /static/, /coverage/], 57 | loader: 'isparta-instrumenter-loader' } 58 | ], 59 | loaders: [ 60 | { 61 | exclude: /node_modules/, 62 | loader: 'babel', 63 | test: /\.js?$/, 64 | /* query: { 65 | presets: [ 'es2015', 'react', 'stage-0' ], 66 | plugins: [ 'transform-decorators-legacy', 'transform-runtime' ] 67 | } */ 68 | } 69 | ] 70 | } 71 | }, 72 | webpackMiddleware: { 73 | noInfo: true, 74 | stats: { 75 | colors: true, 76 | hash: false, 77 | timings: true, 78 | chunks: false, 79 | chunkModules: false, 80 | modules: false 81 | } 82 | }, 83 | mochaReporter: { 84 | colors: { 85 | success: 'bgGreen', 86 | info: 'blue', 87 | warning: 'cyan', 88 | error: 'bgRed' 89 | } 90 | }, 91 | coverageReporter: { 92 | // configure the reporter to use isparta for JavaScript coverage 93 | // Only on { "karma-coverage": "douglasduteil/karma-coverage#next" } 94 | instrumenters: { isparta : require('isparta') }, 95 | instrumenter: { 96 | '**/*.js': 'isparta' 97 | }, 98 | dir: 'coverage/', //path to created html doc 99 | reporters: [ 100 | { type: 'lcovonly', subdir: 'lcovonly', file: 'lcov.info' }, 101 | { type: 'html', subdir: 'html' } 102 | ] 103 | }, 104 | 105 | // web server port 106 | port: 9876, 107 | 108 | 109 | // enable / disable colors in the output (reporters and logs) 110 | colors: true, 111 | 112 | 113 | // level of logging 114 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 115 | logLevel: config.LOG_INFO, 116 | 117 | 118 | // enable / disable watching file and executing tests whenever any file changes 119 | autoWatch: false, 120 | 121 | 122 | // start these browsers 123 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 124 | browsers: ['Firefox'], 125 | 126 | 127 | // Continuous Integration mode 128 | // if true, Karma captures browsers, runs the tests and exits 129 | singleRun: true 130 | }) 131 | } 132 | -------------------------------------------------------------------------------- /nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": [ 3 | "webpack/devServer.js", 4 | "src/api.js", 5 | "src/issueToken.js", 6 | "src/server.js", 7 | "src/controllers", 8 | "src/controllers/**", 9 | "src/extensions/**/controllers/**/**" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hapi-react-starter-kit", 3 | "version": "0.1.6", 4 | "description": "A hapi React Starter kit with react-router, redux, react-transform", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "node node_modules/karma/bin/karma start ./karma.config.js", 8 | "clean": "rimraf ./static/assets rimraf ./coverage", 9 | "start": "node index.js", 10 | "start-dev": "nodemon index.js", 11 | "dev": "cross-env NODE_ENV=development npm run start-dev", 12 | "prod": "cross-env NODE_ENV=production cross-env PORT=8080 npm start", 13 | "build": "rimraf ./static/assets && webpack -p --progress --stats --config ./webpack/prod.config.js", 14 | "postinstall": "npm run build", 15 | "eslint": "eslint .", 16 | "jscs": "jscs .", 17 | "coveralls": "copy coverage/lcovonly/lcov.info | node ./node_modules/coveralls/bin/coveralls" 18 | }, 19 | "author": "Dindaleon", 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/Dindaleon/hapi-react-starter-kit.git" 23 | }, 24 | "homepage": "https://github.com/Dindaleon/hapi-react-starter-kit", 25 | "keywords": [ 26 | "hapi", 27 | "react", 28 | "reactjs", 29 | "react-router", 30 | "react-transform", 31 | "react-intl", 32 | "radium", 33 | "es6", 34 | "boilerplate", 35 | "starterkit", 36 | "isomorphic", 37 | "hot", 38 | "reload", 39 | "hmr", 40 | "live", 41 | "edit", 42 | "webpack", 43 | "openshift", 44 | "babel", 45 | "swagger", 46 | "socketio", 47 | "websockets", 48 | "chat", 49 | "paypal", 50 | "extensions", 51 | "extended" 52 | ], 53 | "license": "MIT", 54 | "dependencies": { 55 | "aguid": "^1.0.3", 56 | "babel-core": "^6.4.5", 57 | "babel-loader": "^6.2.1", 58 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 59 | "babel-plugin-transform-runtime": "^6.4.3", 60 | "babel-preset-es2015": "^6.3.13", 61 | "babel-preset-react": "^6.3.13", 62 | "babel-preset-stage-0": "^6.3.13", 63 | "bell": "^6.0.0", 64 | "bluebird": "^3.1.1", 65 | "cross-env": "^1.0.7", 66 | "fs": "0.0.2", 67 | "hapi": "^12.0.0", 68 | "hapi-auth-cookie": "^6.0.0", 69 | "hapi-auth-jwt2": "^5.1.3", 70 | "hapi-swagger": "^3.0.1", 71 | "history": "^1.12.5", 72 | "hoist-non-react-statics": "^1.0.3", 73 | "inert": "^3.1.0", 74 | "intl": "^1.0.1", 75 | "iron": "^3.0.1", 76 | "isomorphic-fetch": "^2.2.0", 77 | "joi": "^7.1.0", 78 | "jsonwebtoken": "^5.4.0", 79 | "material-ui": "^0.14.1", 80 | "node-forge": "^0.6.37", 81 | "path": "^0.12.7", 82 | "paypal-rest-sdk": "^1.6.8", 83 | "piping-extended": "^0.3.1", 84 | "query-string": "^3.0.0", 85 | "radium": "^0.16.1", 86 | "react": "^0.14.3", 87 | "react-addons-update": "^0.14.3", 88 | "react-dom": "^0.14.0", 89 | "react-helmet": "^2.2.0", 90 | "react-intl": "^2.0.0-beta-2", 91 | "react-redux": "^4.0.0", 92 | "react-router": "^1.0.3", 93 | "react-tap-event-plugin": "^0.2.1", 94 | "redis": "^2.2.5", 95 | "redux": "^3.0.2", 96 | "redux-router": "^1.0.0-beta3", 97 | "rimraf": "^2.4.3", 98 | "serialize-javascript": "^1.1.2", 99 | "socket.io": "^1.3.7", 100 | "socket.io-client": "^1.3.7", 101 | "stats-webpack-plugin": "^0.3.0", 102 | "strip-loader": "^0.1.0", 103 | "vision": "^4.0.1", 104 | "webpack": "^1.12.2", 105 | "webpack-dev-middleware": "^1.2.0", 106 | "webpack-dev-server": "^1.14.1", 107 | "webpack-hot-middleware": "^2.4.1" 108 | }, 109 | "devDependencies": { 110 | "babel-eslint": "^4.1.6", 111 | "babel-jscs": "^2.0.5", 112 | "babel-plugin-react-intl": "^2.0.0", 113 | "babel-plugin-react-transform": "^2.0.0-beta1", 114 | "chalk": "^1.1.1", 115 | "codacy-coverage": "^1.1.3", 116 | "coveralls": "^2.11.4", 117 | "eslint": "^1.7.0", 118 | "eslint-plugin-react": "^3.15.0", 119 | "isparta": "^4.0.0", 120 | "isparta-instrumenter-loader": "^1.0.0", 121 | "istanbul": "^0.4.0", 122 | "jscs": "^2.3.2", 123 | "json-loader": "^0.5.3", 124 | "karma": "^0.13.19", 125 | "karma-babel-preprocessor": "^6.0.1", 126 | "karma-chai": "^0.1.0", 127 | "karma-chrome-launcher": "^0.2.2", 128 | "karma-coverage": "^0.5.2", 129 | "karma-firefox-launcher": "^0.1.6", 130 | "karma-mocha": "^0.2.0", 131 | "karma-mocha-reporter": "^1.1.1", 132 | "karma-phantomjs2-launcher": "^0.4.0", 133 | "karma-sinon-chai": "^1.1.0", 134 | "karma-sourcemap-loader": "^0.3.6", 135 | "karma-webpack": "^1.7.0", 136 | "mocha": "^2.3.3", 137 | "mocha-lcov-reporter": "^1.0.0", 138 | "nodemon": "^1.8.1", 139 | "react-addons-test-utils": "^0.14.5", 140 | "react-transform-catch-errors": "^1.0.0", 141 | "react-transform-hmr": "^1.0.1", 142 | "redbox-react": "^1.1.1", 143 | "sinon-chai": "^2.8.0" 144 | }, 145 | "engines": { 146 | "node": ">= 5.0.0", 147 | "npm": ">= 3.0.0" 148 | }, 149 | "private": false 150 | } 151 | -------------------------------------------------------------------------------- /src/ConnectedIntlProvider.js: -------------------------------------------------------------------------------- 1 | // Connect react-intl Provide (IntlProvider) to the Redux Store 2 | // for hot swapping of locales 3 | import { connect } from 'react-redux'; 4 | import { IntlProvider } from 'react-intl'; 5 | 6 | const mapStateToProps = state => { 7 | return { 8 | // Locale is retrieved from the user's store 9 | // If no user logged in, default to EN (english) 10 | locale: state.user.data.locale, 11 | // Messages are retrieved from the i18l store 12 | messages: state.i18l.messages, 13 | // temp fix en case react doesnt re-render 14 | // key: state.user.data.locale 15 | }; 16 | }; 17 | 18 | export default connect(mapStateToProps)(IntlProvider); 19 | -------------------------------------------------------------------------------- /src/actions/counter.js: -------------------------------------------------------------------------------- 1 | export const INCREMENT_COUNTER = 'INCREMENT_COUNTER'; 2 | export const DECREMENT_COUNTER = 'DECREMENT_COUNTER'; 3 | 4 | export function increment() { 5 | return { 6 | type: INCREMENT_COUNTER 7 | }; 8 | } 9 | 10 | export function decrement() { 11 | return { 12 | type: DECREMENT_COUNTER 13 | }; 14 | } 15 | 16 | export function incrementIfOdd() { 17 | return (dispatch, getState) => { 18 | const { counter } = getState(); 19 | 20 | if (counter % 2 === 0) { 21 | return; 22 | } 23 | 24 | dispatch(increment()); 25 | }; 26 | } 27 | 28 | export function incrementAsync(delay = 1000) { 29 | return dispatch => { 30 | setTimeout(() => { 31 | dispatch(increment()); 32 | }, delay); 33 | }; 34 | } -------------------------------------------------------------------------------- /src/actions/extensionsActions.js: -------------------------------------------------------------------------------- 1 | export const SET_ACTIVE_REDUCERS = 'SET_ACTIVE_REDUCERS'; 2 | export const SET_ALL_EXTENSIONS_DATA_LOADED = 'SET_ALL_EXTENSIONS_DATA_LOADED'; 3 | export const SET_ALL_EXTENSIONS_DATA_CLEARED = 'SET_ALL_EXTENSIONS_DATA_CLEARED'; 4 | 5 | export const setActiveReducers = extensions => { 6 | return { 7 | type: SET_ACTIVE_REDUCERS, 8 | extensions 9 | }; 10 | }; 11 | 12 | export const setAllExtensionsDataLoaded = bool => { 13 | return { 14 | type: SET_ALL_EXTENSIONS_DATA_LOADED, 15 | bool 16 | }; 17 | }; 18 | 19 | export const setAllExtensionsDataCleared = bool => { 20 | return { 21 | type: SET_ALL_EXTENSIONS_DATA_CLEARED, 22 | bool 23 | }; 24 | }; 25 | 26 | export const allExtensionsDataLoaded = ( globalState ) => { 27 | return globalState.extensions && globalState.extensions.allExtensionsDataLoaded; 28 | }; 29 | 30 | export const allExtensionsDataCleared = ( globalState ) => { 31 | return globalState.extensions && globalState.extensions.cleared; 32 | }; 33 | 34 | export const loaded = ( globalState ) => { 35 | return globalState.extensions && globalState.extensions.loadedActiveExtensions; 36 | }; 37 | -------------------------------------------------------------------------------- /src/actions/localeActions.js: -------------------------------------------------------------------------------- 1 | export const LOAD_LOCALE = 'LOAD_LOCALE'; 2 | 3 | export const loadLocale = ( locale ) => { 4 | return { 5 | type: LOAD_LOCALE, 6 | locale 7 | }; 8 | }; 9 | 10 | export const isLocaleLoaded = ( globalState ) => { 11 | return globalState.locale && globalState.locale.loaded; 12 | }; 13 | -------------------------------------------------------------------------------- /src/actions/roomsActions.js: -------------------------------------------------------------------------------- 1 | export const LOAD_MESSAGES = 'LOAD_MESSAGES'; 2 | export const LOAD_MESSAGES_SUCCESS = 'LOAD_MESSAGES_SUCCESS'; 3 | export const LOAD_MESSAGES_FAILURE = 'LOAD_MESSAGES_FAILURE'; 4 | export const RESET_MESSAGES = 'RESET_MESSAGES'; 5 | export const RESET_MESSAGES_SUCCESS = 'RESET_MESSAGES_SUCCESS'; 6 | export const RESET_MESSAGES_FAILURE = 'RESET_MESSAGES_FAILURE'; 7 | export const CLEAN_ROOMS_LIST = 'CLEAN_ROOMS_LIST'; 8 | export const CREATE_ROOM = 'CREATE_ROOM'; 9 | export const CREATE_ROOM_SUCCESS = 'CREATE_ROOM_SUCCESS'; 10 | export const CREATE_ROOM_FAILURE = 'CREATE_ROOM_FAILURE'; 11 | export const LIST_ROOM = 'LIST_ROOM'; 12 | export const LIST_ROOM_SUCCESS = 'LIST_ROOM_SUCCESS'; 13 | export const LIST_ROOM_FAILURE = 'LIST_ROOM_FAILURE'; 14 | 15 | export const loadMessages = ( id ) => { 16 | return { 17 | type: [ LOAD_MESSAGES ], 18 | promise: ( client ) => client.get('/rooms/' + id + '/messages', { 19 | params: { 20 | id 21 | } 22 | }) 23 | }; 24 | }; 25 | 26 | export const resetLoad = () => { 27 | return { 28 | type: [ RESET_MESSAGES ] 29 | }; 30 | }; 31 | 32 | export const cleanRoomsList = () => { 33 | return { 34 | type: CLEAN_ROOMS_LIST 35 | }; 36 | }; 37 | 38 | export const createRoom = ( user, room ) => { 39 | return { 40 | type: [ CREATE_ROOM ], 41 | promise: ( client ) => client.post('/rooms', { 42 | headers: { 43 | 'Accept': 'application/json', 44 | 'Content-Type': 'application/json', 45 | 'Authorization': user.accessToken, 46 | }, 47 | params: { 48 | authorization: true 49 | }, 50 | data: { 51 | userId: parseInt(user.id, 10), 52 | roomName: room.name 53 | } 54 | }) 55 | }; 56 | }; 57 | 58 | export const list = ( query ) => { 59 | return { 60 | type: [ LIST_ROOM ], 61 | promise: ( client ) => client.get('/rooms/list', { 62 | headers: { 63 | 'Accept': 'application/json', 64 | 'Content-Type': 'application/json' 65 | }, 66 | query 67 | }) 68 | }; 69 | }; 70 | 71 | export const ischatLoaded = ( globalState ) => { 72 | return globalState.chat && globalState.chat.loaded; 73 | }; 74 | 75 | export const isListLoaded = ( globalState ) => { 76 | return globalState.chat && globalState.chat.listed; 77 | }; 78 | -------------------------------------------------------------------------------- /src/actions/userActions.js: -------------------------------------------------------------------------------- 1 | export const LOAD = 'LOAD'; 2 | export const LOAD_SUCCESS = 'LOAD_SUCCESS'; 3 | export const LOAD_FAILURE = 'LOAD_FAILURE'; 4 | export const LOGIN = 'LOGIN'; 5 | export const LOGIN_SUCCESS = 'LOGIN_SUCCESS'; 6 | export const LOGIN_FAILURE = 'LOGIN_FAILURE'; 7 | export const LOGOUT = 'LOGOUT'; 8 | export const LOGOUT_SUCCESS = 'LOGOUT_SUCCESS'; 9 | export const LOGOUT_FAILURE = 'LOGOUT_FAILURE'; 10 | export const UPDATE = 'UPDATE'; 11 | export const UPDATE_SUCCESS = 'UPDATE_SUCCESS'; 12 | export const UPDATE_FAILURE = 'UPDATE_FAILURE'; 13 | export const REGISTER = 'REGISTER'; 14 | export const REGISTER_SUCCESS = 'REGISTER_SUCCESS'; 15 | export const REGISTER_FAILURE = 'REGISTER_FAILURE'; 16 | export const SET_COORDINATES = 'SET_COORDINATES'; 17 | export const SET_LOCALE = 'SET_LOCALE'; 18 | export const SET_USER_AGENT = 'SET_USER_AGENT'; 19 | export const SWITCH_LOCALE = 'SWITCH_LOCALE'; 20 | export const SWITCH_LOCALE_SUCCESS = 'SWITCH_LOCALE_SUCCESS'; 21 | export const SWITCH_LOCALE_FAILURE = 'SWITCH_LOCALE_FAILURE'; 22 | 23 | 24 | export const load = ( accessToken ) => { 25 | return { 26 | type: [ LOAD ], 27 | promise: ( client ) => client.get('/oauth/token', { 28 | headers: { 29 | 'Accept': 'application/json', 30 | 'Content-Type': 'application/json', 31 | 'Authorization': accessToken 32 | }, 33 | params: { 34 | authorization: true 35 | } 36 | }) 37 | }; 38 | }; 39 | 40 | export const login = ( username, password ) => { 41 | return { 42 | type: [ LOGIN ], 43 | promise: ( client ) => client.post('/login', { 44 | headers: { 45 | 'Accept': 'application/json', 46 | 'Content-Type': 'application/json' 47 | }, 48 | data: { 49 | username, 50 | password 51 | } 52 | }) 53 | }; 54 | }; 55 | 56 | export const logout = ( sessionId ) => { 57 | return { 58 | type: [ LOGOUT ], 59 | promise: ( client ) => client.post('/logout', { 60 | headers: { 61 | 'sessionid': sessionId 62 | } 63 | }) 64 | }; 65 | }; 66 | 67 | export const register = ( username, password, email ) => { 68 | return { 69 | type: [ REGISTER ], 70 | promise: ( client ) => client.post('users', { 71 | headers: { 72 | 'Accept': 'application/json', 73 | 'Content-Type': 'application/json' 74 | }, 75 | data: { 76 | username, 77 | password, 78 | email 79 | } 80 | }) 81 | }; 82 | }; 83 | 84 | export const update = ( user ) => { 85 | return { 86 | type: [ UPDATE ], 87 | promise: ( client ) => client.put('/users/update', { 88 | headers: { 89 | 'Accept': 'application/json', 90 | 'Content-Type': 'application/json', 91 | 'Authorization': user.accessToken, 92 | }, 93 | params: { 94 | authorization: true 95 | }, 96 | data: { 97 | id: parseInt(user.id, 10), 98 | username: user.username, 99 | email: user.email 100 | } 101 | }) 102 | }; 103 | }; 104 | 105 | export const setCoordinates = ( coordinates ) => { 106 | return { 107 | type: SET_COORDINATES, 108 | coordinates 109 | }; 110 | }; 111 | 112 | export const setLocale = ( locale ) => { 113 | return { 114 | type: SET_LOCALE, 115 | locale 116 | }; 117 | }; 118 | 119 | export const switchLocale = ( user, direct ) => { 120 | return { 121 | type: [ SWITCH_LOCALE ], 122 | promise: client => client.put('/users/update', { 123 | headers: { 124 | 'Accept': 'application/json', 125 | 'Content-Type': 'application/json', 126 | 'Authorization': user.accessToken, 127 | }, 128 | params: { 129 | authorization: true 130 | }, 131 | data: { 132 | id: parseInt(user.id, 10), 133 | locale: user.locale, 134 | switchLocale: direct 135 | } 136 | }) 137 | }; 138 | }; 139 | 140 | export const setUserAgent = ( userAgent ) => { 141 | return { 142 | type: SET_USER_AGENT, 143 | userAgent 144 | }; 145 | }; 146 | 147 | export const isAuthLoaded = ( globalState ) => { 148 | return globalState.user && globalState.user.loaded; 149 | }; 150 | -------------------------------------------------------------------------------- /src/api.js: -------------------------------------------------------------------------------- 1 | // import validateUser from './helpers/validateUser'; 2 | import { validateUser } from './controllers/functions/users'; 3 | import routes from './controllers'; 4 | import config from './config'; 5 | import { getActiveExtensionsSync } from './helpers/getActiveReducers'; 6 | 7 | let extendedApiRoutes = {}; 8 | const activeExtensions = getActiveExtensionsSync(); 9 | for ( const extensionFolderName of activeExtensions) { 10 | let extensionController = null; 11 | try { 12 | extensionController = require('./extensions/' + extensionFolderName + '/controllers/index').default; 13 | extendedApiRoutes[extensionFolderName] = extensionController; 14 | } catch (e) { 15 | // console.log('This extension (' + extensionFolderName + ') does not have controllers.'); 16 | } 17 | } 18 | extendedApiRoutes = Object.assign({}, routes, extendedApiRoutes); 19 | 20 | const register = ( server, options, next ) => { 21 | // Set Auth Strategy 22 | server.auth.strategy( 23 | config.server.auth.strategy, 24 | config.server.auth.scheme, 25 | { 26 | key: config.server.auth.secret, 27 | validateFunc: validateUser, // validate function defined above (imported) 28 | verifyOptions: { 29 | ignoreExpiration: true, 30 | algorithms: [ config.server.auth.algorithms ] } 31 | } 32 | ); 33 | // server.auth.default(config.server.auth.strategy); 34 | 35 | for (const key in extendedApiRoutes) { 36 | if (extendedApiRoutes.hasOwnProperty( key )) { 37 | server.select('api').route(extendedApiRoutes[key]); 38 | } 39 | } 40 | next(); 41 | }; 42 | 43 | register.attributes = { 44 | name: config.api.name, 45 | version: config.api.version 46 | }; 47 | 48 | export default register; 49 | -------------------------------------------------------------------------------- /src/client.js: -------------------------------------------------------------------------------- 1 | // import libraries 2 | import React from 'react'; 3 | import ReactDOM from 'react-dom'; 4 | import { Router } from 'react-router'; 5 | import { 6 | ReduxRouter, 7 | reduxReactRouter 8 | } from 'redux-router'; 9 | 10 | import { Provider } from 'react-redux'; 11 | 12 | import io from 'socket.io-client'; 13 | 14 | import createHistory from 'history/lib/createBrowserHistory'; 15 | import ApiClient from './helpers/ApiClient'; 16 | 17 | // import local files 18 | import getRoutes from './routes'; 19 | import makeRouteHooksSafe from './helpers/makeRouteHooksSafe'; 20 | import configureStore from './store/configureStore'; 21 | import ConnectedIntlProvider from './ConnectedIntlProvider'; 22 | 23 | // import globals 24 | import './globals'; 25 | 26 | // Define constants 27 | const client = new ApiClient(); 28 | const initialState = window.__INITIAL_STATE__; 29 | const activeExtensions = initialState.extensions.enabled; 30 | const store = configureStore( 31 | reduxReactRouter, 32 | makeRouteHooksSafe(getRoutes, activeExtensions), 33 | activeExtensions, 34 | createHistory, 35 | client, 36 | initialState 37 | ); 38 | const root = document.getElementById('root'); 39 | 40 | if ( __DEVELOPMENT__ ) { 41 | console.log('__INITIAL_STATE__ ', initialState); // for checking initial store state on load 42 | // Expose globally 43 | window.React = React; // enable debugger 44 | 45 | if (!root || !root.firstChild || !root.firstChild.attributes || !root.firstChild.attributes['data-react-checksum']) { 46 | console.error('Server-side React render was discarded. Make sure that your initial render does not contain any client-side code.'); 47 | } 48 | } 49 | 50 | const initSocket = () => { 51 | const socket = io( WS_HOST + ':' + WS_PORT ); 52 | return socket; 53 | }; 54 | 55 | // Expose socket io 56 | global.socket = initSocket(); 57 | 58 | const run = () => { 59 | ReactDOM.render( 60 | 61 | 62 | } /> 64 | 65 | , 66 | root 67 | ); 68 | }; 69 | 70 | const load = () => { 71 | if ( window.addEventListener ) { 72 | window.addEventListener( 'DOMContentLoaded', run ); 73 | } else { 74 | window.attachEvent( 'onload', run ); 75 | } 76 | }; 77 | 78 | // Include intl polyfill for Safari browser 79 | if (!global.Intl) { 80 | require.ensure([ 'intl' ], require => { 81 | require('intl'); 82 | load(); 83 | }, 'IntlBundle'); 84 | } else { 85 | load(); 86 | } 87 | -------------------------------------------------------------------------------- /src/components/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { StyleRoot } from 'radium'; 3 | import Promise from 'bluebird'; 4 | import Helmet from 'react-helmet'; 5 | import Layout from '../components/Layout'; 6 | import { 7 | load, 8 | isAuthLoaded 9 | } from '../actions/userActions'; 10 | import { loadLocale } from '../actions/localeActions'; 11 | import { 12 | allExtensionsDataLoaded, 13 | allExtensionsDataCleared, 14 | setAllExtensionsDataLoaded 15 | } from '../actions/extensionsActions'; 16 | import connectData from '../helpers/connectData'; 17 | import loadExtensionsData from '../helpers/loadExtensionsData'; 18 | import { ThemeBody } from '../themes'; 19 | import config from '../config'; 20 | 21 | const fetchData = (getState, dispatch) => { 22 | const promises = []; 23 | 24 | if (!isAuthLoaded(getState())) { 25 | promises.push(dispatch(load()).then(() => { 26 | const user = getState().user.data; 27 | if ( user.id === 0 ) { 28 | // Set default locale for logged out users 29 | user.locale = config.user.locale; 30 | } 31 | // Load user/guest locale 32 | promises.push(dispatch(loadLocale(user.locale))); 33 | })); 34 | } 35 | 36 | return Promise.all(promises); 37 | }; 38 | 39 | const fetchDataDeferred = (getState, dispatch) => { 40 | // Here we load all the required data for the extensions 41 | if (!allExtensionsDataLoaded(getState()) && !allExtensionsDataCleared(getState())) { 42 | return loadExtensionsData(getState(), dispatch) 43 | .then( () => { 44 | dispatch(setAllExtensionsDataLoaded(true)); 45 | }); 46 | } 47 | }; 48 | 49 | @connectData( fetchData, fetchDataDeferred ) 50 | class App extends Component { 51 | state = { 52 | watchId: 0 53 | }; 54 | 55 | componentDidMount() { 56 | // Locale Control 57 | const { loadLocale, setLocale, user } = this.props; 58 | // Load saved locale for guests 59 | if ( user.id === 0 && localStorage.locale ) { 60 | const locale = localStorage.locale; 61 | loadLocale(locale); 62 | setLocale(locale); 63 | } 64 | // Geolocation Control 65 | this.geolocation = navigator.geolocation || false; 66 | let geolocation = this.geolocation; 67 | if (geolocation) { 68 | geolocation = navigator.geolocation; 69 | this.watchId = geolocation.watchPosition(this.geoSuccess, this.geoError); 70 | } 71 | } 72 | 73 | componentWillUnmount() { 74 | if (this.geolocation && this.state.watchId > 0) { 75 | this.geolocation.clearWatch(this.state.watchId); 76 | } 77 | } 78 | 79 | geoSuccess = position => { 80 | const coordinates = { 81 | latitude: position.coords.latitude, 82 | longitude: position.coords.longitude 83 | }; 84 | this.props.setCoordinates(coordinates); 85 | this.setState({ watchId: this.watchId }); 86 | }; 87 | 88 | geoError = () => { 89 | console.log('Sorry, geolocation is not available.'); 90 | }; 91 | 92 | render() { 93 | const { userAgent } = this.props; 94 | return ( 95 | 96 | 97 | 98 | 99 | 100 | ); 101 | } 102 | } 103 | 104 | App.propTypes = { 105 | loadLocale: PropTypes.func.isRequired, 106 | setCoordinates: PropTypes.func.isRequired, 107 | setLocale: PropTypes.func.isRequired, 108 | user: PropTypes.object.isRequired, 109 | userAgent: PropTypes.string.isRequired 110 | }; 111 | 112 | export default App; 113 | -------------------------------------------------------------------------------- /src/components/AppBar.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Theme from '../themes'; 3 | 4 | class Bar extends Component { 5 | state = {}; 6 | 7 | render() { 8 | return ( 9 | 10 | ); 11 | } 12 | } 13 | 14 | export default Bar; 15 | -------------------------------------------------------------------------------- /src/components/Counter.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { StyleRoot } from 'radium'; 3 | import Button from '../themes/default/Button'; 4 | 5 | class Counter extends Component { 6 | render() { 7 | const { increment, incrementIfOdd, incrementAsync, decrement, counter } = this.props; 8 | return ( 9 | 10 | Clicked: { counter } times 11 | { ' ' } 12 | 13 | { ' ' } 14 | 15 | { ' ' } 16 | 17 | { ' ' } 18 | 19 | 20 | ); 21 | } 22 | } 23 | 24 | Counter.propTypes = { 25 | increment: PropTypes.func.isRequired, 26 | incrementIfOdd: PropTypes.func.isRequired, 27 | incrementAsync: PropTypes.func.isRequired, 28 | decrement: PropTypes.func.isRequired, 29 | counter: PropTypes.number.isRequired 30 | }; 31 | 32 | export default Counter; 33 | -------------------------------------------------------------------------------- /src/components/Dashboard.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import update from 'react-addons-update'; 3 | import { defineMessages, FormattedMessage } from 'react-intl'; 4 | import { Link } from 'react-router'; 5 | import Promise from 'bluebird'; 6 | import fetch from 'isomorphic-fetch'; 7 | 8 | import Theme from '../themes'; 9 | Promise.promisifyAll(fetch); 10 | 11 | const messages = defineMessages({ 12 | createRoomButton: { 13 | id: 'rooms.createButton', 14 | description: 'Create Rooms button', 15 | defaultMessage: 'Create Room' 16 | }, 17 | updateUserButton: { 18 | id: 'user.updateButton', 19 | description: 'User Update button', 20 | defaultMessage: 'Update User' 21 | } 22 | }); 23 | 24 | export default class Dashboard extends Component { 25 | 26 | state = { 27 | user: { 28 | id: this.props.user.id, 29 | name: this.props.user.username, 30 | email: this.props.user.email 31 | }, 32 | room: { 33 | name: '' 34 | }, 35 | lists: { 36 | rooms: [] 37 | } 38 | }; 39 | 40 | componentDidMount() { 41 | const query = { 42 | owner: parseInt(this.state.user.id, 10) 43 | }; 44 | this.props.list( query ).then( action => { 45 | if ( typeof action.result !== 'undefined' ) { 46 | this.setState({ lists: { rooms: action.result.data }}); 47 | } 48 | }); 49 | } 50 | 51 | handleSubmitUserUpdate = event => { 52 | event.preventDefault(); 53 | const { user } = this.props; 54 | const _user = { 55 | id: this.state.user.id, 56 | username: this.state.user.name, 57 | email: this.state.user.email, 58 | accessToken: user.accessToken 59 | }; 60 | this.props.update( _user ).then(() => { 61 | // User updated... 62 | // Do stuff after updating user 63 | }); 64 | }; 65 | 66 | handleSubmitCreateRoom = event => { 67 | event.preventDefault(); 68 | const { user } = this.props; 69 | const _user = { 70 | id: this.state.user.id, 71 | accessToken: user.accessToken 72 | }; 73 | const roomName = this.state.room.name; 74 | this.setState({ room: { name: '' }}); 75 | const _room = { 76 | name: roomName 77 | }; 78 | 79 | this.props.createRoom( _user, _room ).then( action => { 80 | if ( typeof action.result === 'undefined') { 81 | // Handle error 82 | // console.log('User\'s credentials have expired.'); 83 | } else { 84 | const rooms = this.state.lists.rooms; 85 | rooms.push({ 86 | id: action.result.data.id, 87 | name: action.result.data.name 88 | }); 89 | this.setState({ lists: { rooms }}); 90 | } 91 | }); 92 | }; 93 | 94 | handleChangeUser = event => { 95 | this.setState( 96 | update(this.state, 97 | { user: { [event.target.name]: { $set: event.target.value }}} 98 | ) 99 | ); 100 | }; 101 | 102 | handleChangeRoom = event => { 103 | this.setState( 104 | update(this.state, 105 | { room: { [event.target.name]: { $set: event.target.value }}} 106 | ) 107 | ); 108 | }; 109 | 110 | render() { 111 | return ( 112 |
113 | Edit user: 114 |
115 | 120 | 125 | 127 | 128 | 129 | 130 | Create room: 131 |
132 | 137 | 139 | 140 | 141 | 142 |
    143 | { typeof this.state.lists.rooms.map === 'function' ? 144 | this.state.lists.rooms.map( ( room, index ) => { 145 | return ( 146 |
  • 147 | { room.name } 148 |
  • 149 | ); 150 | }) 151 | : null } 152 |
153 |
154 | ); 155 | } 156 | } 157 | 158 | Dashboard.propTypes = { 159 | user: PropTypes.object.isRequired, 160 | rooms: PropTypes.object.isRequired, 161 | createRoom: PropTypes.func.isRequired, 162 | update: PropTypes.func.isRequired, 163 | list: PropTypes.func.isRequired 164 | }; 165 | -------------------------------------------------------------------------------- /src/components/EmptyComponent.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | class EmptyComponent extends Component { 4 | render() { 5 | return ( 6 |
7 | ); 8 | } 9 | } 10 | 11 | export default EmptyComponent; 12 | -------------------------------------------------------------------------------- /src/components/Hall.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { Link } from 'react-router'; 3 | import { defineMessages, FormattedMessage } from 'react-intl'; 4 | 5 | const messages = defineMessages({ 6 | rooms: { 7 | id: 'rooms.rooms', 8 | description: 'rooms', 9 | defaultMessage: 'Rooms' 10 | }, 11 | noRoomsAvailable: { 12 | id: 'rooms.noRoomsAvailable', 13 | description: 'No more rooms available.', 14 | defaultMessage: 'No rooms available.' 15 | } 16 | }); 17 | 18 | export default class Hall extends Component { 19 | 20 | state = { 21 | lists: { 22 | rooms: [] 23 | } 24 | }; 25 | 26 | componentDidMount() { 27 | this.props.list().then( action => { 28 | this.setState({ lists: { rooms: action.result.data }}); 29 | }); 30 | } 31 | 32 | render() { 33 | return ( 34 |
35 | { this.state.lists.rooms.length } 36 |
    37 | { 38 | typeof this.state.lists.rooms.map === 'function' 39 | ? 40 | this.state.lists.rooms.map( (room, index) => { 41 | return
  • { room.name }
  • ; 42 | }) 43 | : 44 | 45 | } 46 |
47 |
48 | ); 49 | } 50 | } 51 | 52 | Hall.propTypes = { 53 | list: PropTypes.func.isRequired, 54 | loadMessages: PropTypes.func.isRequired, 55 | rooms: PropTypes.object.isRequired, 56 | user: PropTypes.object.isRequired 57 | }; 58 | -------------------------------------------------------------------------------- /src/components/Layout.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import injectTapEventPlugin from 'react-tap-event-plugin'; 3 | import Logout from './auth/Logout'; 4 | import SwitchLocale from './Locale'; 5 | import RenderExtension from '../extensions'; 6 | import AppBar from './AppBar'; 7 | import Menu from './Menu'; 8 | import config from '../config'; 9 | // Needed for onTouchTap 10 | // Can go away when react 1.0 release 11 | // Check this repo: 12 | // https://github.com/zilverline/react-tap-event-plugin 13 | injectTapEventPlugin(); 14 | class Layout extends Component { 15 | render() { 16 | const { 17 | cleanRoomsList, 18 | dispatch, 19 | setAllExtensionsDataCleared, 20 | setAllExtensionsDataLoaded, 21 | extensions, 22 | router, 23 | user, 24 | logout, 25 | loadLocale, 26 | pushState, 27 | setLocale, 28 | globalState 29 | } = this.props; 30 | const childrenProps = this.props; 31 | const childrenWithProps = React.Children.map(this.props.children, child => { 32 | return React.cloneElement(child, { 'children': childrenProps }); 33 | }); 34 | return ( 35 |
36 | 37 | 38 |
39 | SESSIONID: { user.sessionId ? user.sessionId : 'null' } 40 |
41 | YOUR COORDINATES: { 'LAT:' + user.coordinates.latitude + ' LNG:' + user.coordinates.longitude } 42 | 43 | 44 |
45 | 46 | { user.sessionId ? 47 | : '' 58 | } 59 | 60 | { /* this.props.children */} 61 | { childrenWithProps } 62 | 63 |
64 | ); 65 | } 66 | } 67 | 68 | Layout.propTypes = { 69 | children: PropTypes.oneOfType([ 70 | PropTypes.array, 71 | PropTypes.object 72 | ]).isRequired, 73 | cleanRoomsList: PropTypes.func.isRequired, 74 | dispatch: PropTypes.func.isRequired, 75 | setAllExtensionsDataCleared: PropTypes.func.isRequired, 76 | setAllExtensionsDataLoaded: PropTypes.func.isRequired, 77 | extensions: PropTypes.object.isRequired, 78 | loadLocale: PropTypes.func.isRequired, 79 | login: PropTypes.func.isRequired, 80 | logout: PropTypes.func.isRequired, 81 | pushState: PropTypes.func.isRequired, 82 | router: PropTypes.object.isRequired, 83 | setLocale: PropTypes.func.isRequired, 84 | globalState: PropTypes.object.isRequired, 85 | switchLocale: PropTypes.func.isRequired, 86 | user: PropTypes.object.isRequired 87 | }; 88 | 89 | export default Layout; 90 | -------------------------------------------------------------------------------- /src/components/Locale.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | 3 | export default class Locale extends Component { 4 | // Change locale on user input 5 | handleLocaleOnChange = event => { 6 | const user = { 7 | accessToken: this.props.user.accessToken, 8 | id: this.props.user.id, 9 | locale: event.target.value 10 | }; 11 | if ( user.id === 0 ) { 12 | this.props.loadLocale(user.locale); 13 | this.props.setLocale(user.locale); 14 | if (window.localStorage) { 15 | localStorage.setItem('locale', user.locale); 16 | } 17 | } else { 18 | this.props.switchLocale( user, true ).then(() => { 19 | this.props.loadLocale(user.locale); 20 | }); 21 | } 22 | }; 23 | 24 | render() { 25 | return ( 26 |
27 | user locale is { ' ' } 28 | {/* TODO: Convert to array of available languages */} 29 | 34 |
35 | ); 36 | } 37 | } 38 | 39 | Locale.propTypes = { 40 | children: PropTypes.oneOfType([ 41 | PropTypes.array, 42 | PropTypes.object 43 | ]).isRequired, 44 | loadLocale: PropTypes.func.isRequired, 45 | logout: PropTypes.func.isRequired, 46 | pushState: PropTypes.func.isRequired, 47 | setLocale: PropTypes.func.isRequired, 48 | switchLocale: PropTypes.func.isRequired, 49 | user: PropTypes.object.isRequired 50 | }; 51 | -------------------------------------------------------------------------------- /src/components/Menu.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import radium, { StyleRoot } from 'radium'; 3 | import { defineMessages, injectIntl, intlShape } from 'react-intl'; 4 | import Theme from '../themes'; 5 | import customRequire from '../helpers/customRequire'; 6 | 7 | const messages = defineMessages({ 8 | menuIndexButton: { 9 | id: 'menuItem.index', 10 | description: 'Index Menu button', 11 | defaultMessage: 'Index' 12 | }, 13 | menuHomeButton: { 14 | id: 'menuItem.home', 15 | description: 'Home Menu button', 16 | defaultMessage: 'Home' 17 | }, 18 | menuRoomsButton: { 19 | id: 'menuItem.rooms', 20 | description: 'Rooms Menu button', 21 | defaultMessage: 'Rooms' 22 | }, 23 | menuDashboardButton: { 24 | id: 'menuItem.dashboard', 25 | description: 'Dashboard Menu button', 26 | defaultMessage: 'Dashboard' 27 | }, 28 | menuLoginButton: { 29 | id: 'menuItem.login', 30 | description: 'Login Menu button', 31 | defaultMessage: 'Login' 32 | }, 33 | menuSignupButton: { 34 | id: 'menuItem.signup', 35 | description: 'Signup Menu button', 36 | defaultMessage: 'Register' 37 | } 38 | }); 39 | // TODO improve loading of menu items 40 | // by using reducers 41 | class Menu extends Component { 42 | // get active extensions 43 | // check those who have menus 44 | // loop through them 45 | // push menu items 46 | loggedInMenuItems = _extendedItems => { 47 | let extendedItems = []; 48 | const { formatMessage } = this.props.intl; 49 | const loggedInMenuItems = [ 50 | { text: formatMessage(messages.menuIndexButton), to: '/' }, 51 | { text: formatMessage(messages.menuHomeButton), to: '/home' }, 52 | { text: formatMessage(messages.menuRoomsButton), to: '/hall' }, 53 | { text: formatMessage(messages.menuDashboardButton), to: '/dashboard' } 54 | ]; 55 | if ( _extendedItems && _extendedItems.constructor === Array ) { 56 | extendedItems = _extendedItems; 57 | for ( const menuItem of extendedItems ) { 58 | loggedInMenuItems.push(menuItem); 59 | } 60 | } 61 | return loggedInMenuItems; 62 | }; 63 | loggedOutMenuItems = _extendedItems => { 64 | let extendedItems = []; 65 | const { formatMessage } = this.props.intl; 66 | const loggedOutMenuItems = [ 67 | { text: formatMessage(messages.menuIndexButton), to: '/' }, 68 | { text: formatMessage(messages.menuHomeButton), to: '/home' }, 69 | { text: formatMessage(messages.menuLoginButton), to: '/login' }, 70 | { text: formatMessage(messages.menuSignupButton), to: '/register' } 71 | ]; 72 | if ( _extendedItems && _extendedItems.constructor === Array ) { 73 | extendedItems = _extendedItems; 74 | for ( const menuItem of extendedItems ) { 75 | loggedOutMenuItems.push(menuItem); 76 | } 77 | } 78 | return loggedOutMenuItems; 79 | }; 80 | 81 | loadMenuItemsFromExtensions(loggedIn) { 82 | const { formatMessage } = this.props.intl; 83 | const { extensions } = this.props; 84 | let currentExtension = null; 85 | let menuItems = []; 86 | const loggedInItems = []; 87 | const loggedOutItems = []; 88 | for ( const extension of extensions.enabled ) { 89 | try { 90 | currentExtension = customRequire('/' + extension.folderName + '/lang/index'); 91 | if (currentExtension.menu) { 92 | for (const menuItem of currentExtension.menu) { 93 | if (menuItem.translated) { 94 | try { 95 | menuItem.text = formatMessage(currentExtension.messages[menuItem.translated]); 96 | } catch (e) { 97 | console.error('Translated message for menu item "' + menuItem.text + '" is not defined.'); 98 | } 99 | } 100 | for (const scope of menuItem.scopes) { 101 | if (scope === 'user') { 102 | // Add item to loggedInMenuItems 103 | loggedInItems.push({ text: menuItem.text, to: menuItem.to }); 104 | } else { 105 | // Add item to loggedOutMenuItems 106 | loggedOutItems.push({ text: menuItem.text, to: menuItem.to }); 107 | } 108 | } 109 | } 110 | } 111 | } catch (e) { 112 | // console.log('This extension does not contain a menu.') 113 | } 114 | } 115 | if (loggedIn) { 116 | menuItems = this.loggedInMenuItems(loggedInItems); 117 | } else { 118 | menuItems = this.loggedOutMenuItems(loggedOutItems); 119 | } 120 | return menuItems; 121 | }; 122 | 123 | render() { 124 | const { pushState, router, sessionId } = this.props; 125 | 126 | let items = this.loadMenuItemsFromExtensions(false); 127 | if (sessionId) { 128 | items = this.loadMenuItemsFromExtensions(true); 129 | } 130 | 131 | return ( 132 | 133 | 134 | 135 | ); 136 | } 137 | } 138 | 139 | Menu.propTypes = { 140 | extensions: PropTypes.object.isRequired, 141 | intl: intlShape.isRequired, 142 | pushState: PropTypes.func.isRequired, 143 | router: PropTypes.object.isRequired, 144 | sessionId: PropTypes.oneOfType([ 145 | PropTypes.string, 146 | PropTypes.number 147 | ]).isRequired 148 | }; 149 | 150 | export default radium(injectIntl(Menu)); 151 | -------------------------------------------------------------------------------- /src/components/auth/Login.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import radium from 'radium'; 3 | import { defineMessages, FormattedMessage } from 'react-intl'; 4 | import { setCookie } from '../../helpers/cookieTools'; 5 | import config from '../../config'; 6 | import Theme from '../../themes'; 7 | import loadExtensionsData from '../../helpers/loadExtensionsData'; 8 | 9 | const messages = defineMessages({ 10 | loginButton: { 11 | id: 'auth.loginButton', 12 | description: 'Login button.', 13 | defaultMessage: 'Login' 14 | } 15 | }); 16 | 17 | class Login extends Component { 18 | 19 | state = { 20 | userId: 0, 21 | username: null, 22 | password: null, 23 | loggedIn: false, 24 | resultMessage: '' 25 | }; 26 | 27 | handleChangeLoginFields = event => { 28 | this.setState({ 29 | [event.target.name]: event.target.value 30 | }); 31 | }; 32 | 33 | handleLogin = event => { 34 | event.preventDefault(); 35 | const { 36 | dispatch, 37 | setAllExtensionsDataLoaded, 38 | loadLocale, 39 | login, 40 | pushState 41 | } = this.props; 42 | const username = this.state.username || null; 43 | const password = this.state.password || null; 44 | 45 | if (username === null) { 46 | return this.setState({ resultMessage: 'Must enter a username.' }); 47 | } 48 | 49 | if (password === null) { 50 | return this.setState({ resultMessage: 'Must enter a password.' }); 51 | } 52 | login(username, password) 53 | .then( action => { 54 | if ( typeof action.result !== 'undefined' ) { 55 | loadLocale(action.result.data.locale); 56 | // TODO user levels/scopes 57 | return loadExtensionsData(this.props.globalState, dispatch) 58 | .then( () => { 59 | setAllExtensionsDataLoaded(true); 60 | setCookie( 61 | config.user.session.name, // User session 62 | action.result.data.encrypted, // encrypted data 63 | config.user.session.ttls // time to live 64 | ); 65 | // Redirect user 66 | const redirectTo = config.user.redirectOnLogin || 'dashboard'; 67 | pushState(null, '/' + redirectTo); 68 | }).catch( e => { 69 | console.error('ERROR: There was a problem loading extensions\' data.', e.stack); 70 | }); 71 | } 72 | // handle error 73 | return this.setState({ resultMessage: 'There was a problem logging in.' }); 74 | }).catch( e => { 75 | console.error('ERROR: There was a problem logging in.', e.stack); 76 | }); 77 | }; 78 | 79 | render() { 80 | return ( 81 |
82 | 83 |
84 | 85 | 86 | 87 | 88 |
89 | { this.state.resultMessage } 90 | 91 | ); 92 | } 93 | } 94 | 95 | Login.propTypes = { 96 | dispatch: PropTypes.func.isRequired, 97 | globalState: PropTypes.object.isRequired, 98 | loadLocale: PropTypes.func.isRequired, 99 | login: PropTypes.func.isRequired, 100 | pushState: PropTypes.func.isRequired, 101 | setAllExtensionsDataLoaded: PropTypes.func.isRequired 102 | }; 103 | 104 | export default radium(Login); 105 | -------------------------------------------------------------------------------- /src/components/auth/Logout.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { StyleRoot } from 'radium'; 3 | import { defineMessages, FormattedMessage } from 'react-intl'; 4 | import { getCookie, deleteCookie } from '../../helpers/cookieTools'; 5 | import config from '../../config'; 6 | import Theme from '../../themes'; 7 | import clearExtensionsData from '../../helpers/clearExtensionsData'; 8 | 9 | const messages = defineMessages({ 10 | logoutButton: { 11 | id: 'auth.logoutButton', 12 | description: 'Logout button', 13 | defaultMessage: 'Logout' 14 | } 15 | }); 16 | 17 | class Logout extends Component { 18 | handleLogout = () => { 19 | const { 20 | cleanRoomsList, 21 | dispatch, 22 | setAllExtensionsDataLoaded, 23 | setAllExtensionsDataCleared, 24 | logout, 25 | sessionId, 26 | loadLocale, 27 | setLocale, 28 | pushState 29 | } = this.props; 30 | logout(sessionId) 31 | .then(() => { 32 | if (getCookie(config.user.session.name)) { 33 | deleteCookie(config.user.session.name); 34 | } 35 | if (window.localStorage && localStorage.locale) { 36 | loadLocale(localStorage.locale); 37 | setLocale(localStorage.locale); 38 | } else { 39 | // Set default locale after logout 40 | loadLocale(config.user.locale); 41 | } 42 | // Clear all extensions data 43 | return clearExtensionsData(this.props.globalState, dispatch) 44 | .then( () => { 45 | setAllExtensionsDataLoaded(false); 46 | setAllExtensionsDataCleared(true); 47 | // Clean reducers 48 | cleanRoomsList(); 49 | pushState(null, '/'); 50 | }).catch( e => { 51 | console.error('ERROR: There was a problem clearing out extensions\' data.', e.stack); 52 | }); 53 | }); 54 | }; 55 | render() { 56 | return ( 57 | 58 | 59 | 60 | ); 61 | } 62 | } 63 | 64 | Logout.propTypes = { 65 | cleanRoomsList: PropTypes.func.isRequired, 66 | dispatch: PropTypes.func.isRequired, 67 | setAllExtensionsDataLoaded: PropTypes.func.isRequired, 68 | setAllExtensionsDataCleared: PropTypes.func.isRequired, 69 | globalState: PropTypes.object.isRequired, 70 | loadLocale: PropTypes.func.isRequired, 71 | logout: PropTypes.func.isRequired, 72 | pushState: PropTypes.func.isRequired, 73 | sessionId: PropTypes.oneOfType([ 74 | PropTypes.string, 75 | PropTypes.number 76 | ]).isRequired, 77 | setLocale: PropTypes.func.isRequired 78 | }; 79 | 80 | export default Logout; 81 | -------------------------------------------------------------------------------- /src/components/auth/Signup.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { defineMessages, FormattedMessage } from 'react-intl'; 3 | import { setCookie } from '../../helpers/cookieTools'; 4 | import config from '../../config'; 5 | import Theme from '../../themes'; 6 | import loadExtensionsData from '../../helpers/loadExtensionsData'; 7 | 8 | const messages = defineMessages({ 9 | passwordText: { 10 | id: 'global.password', 11 | description: 'Password text', 12 | defaultMessage: 'password' 13 | }, 14 | signupButton: { 15 | id: 'auth.signupButton', 16 | description: 'Signup button.', 17 | defaultMessage: 'Signup' 18 | }, 19 | usernameText: { 20 | id: 'global.username', 21 | description: 'Username text', 22 | defaultMessage: 'username' 23 | } 24 | }); 25 | 26 | export default class Signup extends Component { 27 | 28 | state = { 29 | userId: 0, 30 | username: null, 31 | password: null, 32 | email: null, 33 | loggedIn: false, 34 | resultMessage: '' 35 | }; 36 | 37 | handleChangeRegisterFields(event) { 38 | this.setState({ 39 | [event.target.name]: event.target.value 40 | }); 41 | }; 42 | 43 | handleRegister = event => { 44 | event.preventDefault(); 45 | 46 | const { 47 | dispatch, 48 | register, 49 | login, 50 | loadLocale, 51 | pushState, 52 | setAllExtensionsDataLoaded 53 | } = this.props; 54 | const username = this.state.username || null; 55 | const password = this.state.password || null; 56 | const email = this.state.email || null; 57 | if (username === null) { 58 | return this.setState({ resultMessage: 'Must enter a username.' }); 59 | } 60 | 61 | if (password === null) { 62 | return this.setState({ resultMessage: 'Must enter a password.' }); 63 | } 64 | 65 | if (email === null) { 66 | return this.setState({ resultMessage: 'Must enter an email.' }); 67 | } 68 | 69 | return register( username, password, email ) 70 | .then( action => { 71 | if ( typeof action.result !== 'undefined' ) { 72 | loadLocale(action.result.data.locale || config.user.locale); 73 | // Log the user in ig registration is successful 74 | return login( action.result.data.username, action.result.data.password ) 75 | .then( action => { 76 | // TODO user levels/scopes 77 | return loadExtensionsData(this.props.globalState, dispatch) 78 | .then( () => { 79 | setAllExtensionsDataLoaded(true); 80 | setCookie( 81 | config.user.session.name, // User session 82 | action.result.data.encrypted, // encrypted data 83 | config.user.session.ttls // time to live 84 | ); 85 | // Redirect user 86 | const redirectTo = config.user.redirectOnRegister || 'dashboard'; 87 | pushState(null, '/' + redirectTo); 88 | }).catch( e => { 89 | console.error('ERROR: There was a problem loading extensions\' data.', e.stack); 90 | }); 91 | }); 92 | } 93 | // handle signup error 94 | return this.setState({ resultMessage: 'There was a problem creating a new account.' }); 95 | }).catch( e => { 96 | console.error('ERROR: There was a problem creating a new account.', e.stack); 97 | }); 98 | }; 99 | 100 | render() { 101 | return ( 102 |
103 | 104 |
105 | 106 |
107 | 108 | 109 |
110 | { this.state.resultMessage } 111 | 112 | ); 113 | } 114 | 115 | } 116 | 117 | Signup.propTypes = { 118 | dispatch: PropTypes.func.isRequired, 119 | globalState: PropTypes.object.isRequired, 120 | loadLocale: PropTypes.func.isRequired, 121 | login: PropTypes.func.isRequired, 122 | pushState: PropTypes.func.isRequired, 123 | register: PropTypes.func.isRequired, 124 | setAllExtensionsDataLoaded: PropTypes.func.isRequired 125 | }; 126 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | app: { 3 | title: 'Hapi React SK', 4 | description: 'A hapi React Starter kit with react-router, redux, react-transform.', 5 | head: { 6 | titleTemplate: 'Hapi React SK: %s', 7 | meta: [ 8 | { name: 'description', content: 'A hapi React Starter kit with react-router, redux, react-transform.' }, 9 | { name: 'viewport', content: 'width=device-width, initial-scale=1.0' }, 10 | { charset: 'utf-8' }, 11 | { property: 'og:site_name', content: 'Hapi React Starter Kit' }, 12 | { property: 'og:locale', content: 'en_US' }, 13 | { property: 'og:title', content: 'Hapi React Starter Kit' }, 14 | { property: 'og:description', content: 'A hapi React Starter kit with react-router, redux, react-transform.' }, 15 | { property: 'og:card', content: 'summary' }, 16 | { property: 'og:site', content: '@Dindaleon' }, 17 | { property: 'og:creator', content: '@Dindaleon' }, 18 | { property: 'og:title', content: 'Hapi React Starter Kit' }, 19 | // { property: 'og:image', content: '' }, 20 | // { property: 'og:image:width', content: '200' }, 21 | // { property: 'og:image:height', content: '200' } 22 | ], 23 | link: [ 24 | { rel: 'shortcut icon', href: '/favicon.ico' } 25 | ] 26 | }, 27 | theme: { 28 | name: 'default' 29 | } 30 | }, 31 | server: { 32 | auth: { 33 | scheme: 'jwt', 34 | strategy: 'jwt_token', 35 | // Never Share your secret key 36 | // Generate your own key at: https://www.grc.com/passwords.htm 37 | secret: '24CC51A1D75D4736AEB38782617DEDC02E7A79ECD6FA9D49545386B138AC6658', 38 | // Pick a strong algorithm 39 | algorithms: 'HS256' 40 | }, 41 | // Change these to fit your needs. 42 | protocol: 'http://', 43 | developmentUrl: 'http://localhost:3000', 44 | productionUrl: 'http://localhost:8080', 45 | // productionUrl: 'http://hapi-reactstarterkit.rhcloud.com', 46 | // Websockets server 47 | ws: { 48 | // replace with your production host 49 | host: 'http://hapi-reactstarterkit.rhcloud.com', 50 | port: 8000 51 | } 52 | }, 53 | user: { 54 | token: { 55 | expiresIn: 4500 // seconds for expiring a user's session 56 | }, 57 | session: { 58 | name: 'USER_SESSION', 59 | ttl: 24 * 60 * 60 * 1000 // one day 60 | }, 61 | // Default locale for logged out user (Guest) 62 | locale: 'en', 63 | // Specify where to redirect the user after signin 64 | redirectOnLogin: 'home', 65 | // Specify where to redirect the user after signup 66 | redirectOnRegister: 'dashboard' 67 | }, 68 | redis: { 69 | PASSWORD: 'ZTNiMGM0NDI5OGZjMWMxNDlhZmJmNGM4OTk2ZmI5', 70 | DBNUMBER: 1, // select redis database number. Default: 0 71 | key: { 72 | users: 'users', 73 | rooms: 'chatRooms' 74 | } 75 | }, 76 | api: { 77 | name: 'api', 78 | routes: { 79 | path: '/api/v1' 80 | }, 81 | version: '0.0.1', 82 | swagger: { 83 | documentationPath: '/api/v1/documentation', 84 | endpoint: '/api/v1/docs' 85 | } 86 | }, 87 | iron: { 88 | // Generate your own key at: https://www.grc.com/passwords.htm 89 | secret: '24CC51A1D75D4736AEB38782617DEDC02E7A79ECD6FA9D49545386B138AC6658' 90 | } 91 | }; 92 | 93 | export default config; 94 | -------------------------------------------------------------------------------- /src/containers/AppContainer.js: -------------------------------------------------------------------------------- 1 | import { bindActionCreators } from 'redux'; 2 | import { connect } from 'react-redux'; 3 | import { pushState } from 'redux-router'; 4 | import App from '../components/App'; 5 | import * as ExtensionsActions from '../actions/extensionsActions'; 6 | import * as UserActions from '../actions/userActions'; 7 | import * as RoomsActions from '../actions/roomsActions'; 8 | import * as LocaleActions from '../actions/localeActions'; 9 | import radium from 'radium'; 10 | const mapStateToProps = ( state ) => { 11 | return { 12 | extensions: state.extensions, 13 | globalState: state, 14 | pushState, 15 | router: state.router, 16 | user: state.user.data, 17 | userAgent: state.user.agent, 18 | }; 19 | }; 20 | 21 | const mapDispatchToProps = ( dispatch ) => { 22 | return bindActionCreators({ ...ExtensionsActions, ...UserActions, ...RoomsActions, ...LocaleActions, pushState, dispatch }, dispatch ); 23 | }; 24 | 25 | export default connect( mapStateToProps, mapDispatchToProps )( radium(App) ); 26 | -------------------------------------------------------------------------------- /src/containers/CounterApp.js: -------------------------------------------------------------------------------- 1 | import { bindActionCreators } from 'redux'; 2 | import { connect } from 'react-redux'; 3 | import Counter from '../components/Counter'; 4 | import * as CounterActions from '../actions/counter'; 5 | 6 | const mapStateToProps = ( state ) => { 7 | return { 8 | counter: state.counter, 9 | routerState: state.router 10 | }; 11 | }; 12 | 13 | const mapDispatchToProps = ( dispatch ) => { 14 | return bindActionCreators( CounterActions, dispatch ); 15 | }; 16 | 17 | export default connect( mapStateToProps, mapDispatchToProps )( Counter ); 18 | -------------------------------------------------------------------------------- /src/containers/DashboardContainer.js: -------------------------------------------------------------------------------- 1 | import { bindActionCreators } from 'redux'; 2 | import { connect } from 'react-redux'; 3 | import Dashboard from '../components/Dashboard'; 4 | import * as UserActions from '../actions/userActions'; 5 | import * as RoomsActions from '../actions/roomsActions'; 6 | 7 | const mapStateToProps = ( state ) => { 8 | return { 9 | user: state.user.data, 10 | rooms: state.rooms 11 | }; 12 | }; 13 | 14 | const mapDispatchToProps = ( dispatch ) => { 15 | return bindActionCreators({ ...UserActions, ...RoomsActions }, dispatch ); 16 | }; 17 | 18 | export default connect( mapStateToProps, mapDispatchToProps )( Dashboard ); 19 | -------------------------------------------------------------------------------- /src/containers/HallContainer.js: -------------------------------------------------------------------------------- 1 | import { bindActionCreators } from 'redux'; 2 | import { connect } from 'react-redux'; 3 | import Hall from '../components/Hall'; 4 | import * as RoomsActions from '../actions/roomsActions'; 5 | 6 | const mapStateToProps = ( state ) => { 7 | return { 8 | rooms: state.rooms, 9 | user: state.user.data 10 | }; 11 | }; 12 | 13 | const mapDispatchToProps = ( dispatch ) => { 14 | return bindActionCreators( RoomsActions, dispatch ); 15 | }; 16 | 17 | export default connect( mapStateToProps, mapDispatchToProps )( Hall ); 18 | -------------------------------------------------------------------------------- /src/containers/LoginPageContainer.js: -------------------------------------------------------------------------------- 1 | import { bindActionCreators } from 'redux'; 2 | import { connect } from 'react-redux'; 3 | import LoginPage from '../pages/Login'; 4 | import * as ExtensionsActions from '../actions/extensionsActions'; 5 | import * as UserActions from '../actions/userActions'; 6 | import * as LocaleActions from '../actions/localeActions'; 7 | 8 | const mapStateToProps = ( state ) => { 9 | return { 10 | user: state.user.data, 11 | extensions: state.extensions 12 | }; 13 | }; 14 | 15 | const mapDispatchToProps = ( dispatch ) => { 16 | return bindActionCreators({ ...ExtensionsActions, ...UserActions, ...LocaleActions }, dispatch ); 17 | }; 18 | 19 | export default connect( mapStateToProps, mapDispatchToProps )( LoginPage ); 20 | -------------------------------------------------------------------------------- /src/containers/RoomContainer.js: -------------------------------------------------------------------------------- 1 | import { bindActionCreators } from 'redux'; 2 | import { connect } from 'react-redux'; 3 | import Room from '../components/Room'; 4 | import * as RoomsActions from '../actions/roomsActions'; 5 | 6 | const mapStateToProps = ( state ) => { 7 | return { 8 | rooms: state.rooms, 9 | user: state.user.data 10 | }; 11 | }; 12 | 13 | const mapDispatchToProps = ( dispatch ) => { 14 | return bindActionCreators( RoomsActions, dispatch ); 15 | }; 16 | 17 | export default connect( mapStateToProps, mapDispatchToProps )( Room ); 18 | -------------------------------------------------------------------------------- /src/controllers/index.js: -------------------------------------------------------------------------------- 1 | import users from './users'; 2 | import rooms from './rooms'; 3 | 4 | const routes = { 5 | users, 6 | rooms 7 | }; 8 | 9 | export default routes; 10 | -------------------------------------------------------------------------------- /src/controllers/rooms.js: -------------------------------------------------------------------------------- 1 | import Joi from 'joi'; 2 | import rooms from './functions/rooms'; 3 | 4 | /* 5 | * Hapi Routes for controlling chats 6 | * - CRUD rooms 7 | * - Read/Write messages 8 | */ 9 | const chatRoomController = [ 10 | /* 11 | * Create rooms 12 | */ 13 | { 14 | method: 'POST', 15 | path: '/rooms', 16 | config: { 17 | tags: [ 'api', 'chat', 'rooms' ], 18 | description: 'Create a chat room', 19 | validate: { 20 | headers: Joi.object({ 21 | authorization: Joi.string().required() 22 | }).unknown(), 23 | payload: { 24 | userId: Joi.number().integer().required(), 25 | roomName: Joi.string().min(4).max(24).required(), 26 | description: Joi.string() 27 | } 28 | }, 29 | plugins: { 30 | 'hapi-swagger': { 31 | responseMessages: [ 32 | { 'code': 401, 'message': 'Unauthorized' }, 33 | { 'code': 503, 'message': 'Service Unavailable' } 34 | ] 35 | } 36 | }, 37 | auth: 'jwt_token' 38 | }, 39 | handler: ( request, reply ) => { 40 | rooms.createRoom( request, result => { 41 | if ( result !== null ) { 42 | reply({ 43 | statusCode: 200, 44 | message: 'Room Created Successfully.', 45 | data: result 46 | 47 | }).code(200); 48 | } else { 49 | reply({ 50 | statusCode: 500, 51 | message: 'Something Bad Happened.', 52 | data: { 53 | result 54 | } 55 | }).code(500); 56 | } 57 | }); 58 | } 59 | }, 60 | /* 61 | * List rooms 62 | */ 63 | { 64 | method: 'GET', 65 | path: '/rooms/list', 66 | config: { 67 | tags: [ 'api', 'room', 'list' ], 68 | description: 'Returns a list of rooms.', 69 | validate: { 70 | query: { 71 | offset: Joi 72 | .number() 73 | .integer() 74 | .description('Get rooms from offset.'), 75 | limit: Joi 76 | .number() 77 | .integer() 78 | .description('Limit number of rooms returned.'), 79 | ids: Joi 80 | .array() 81 | .unique() 82 | .description('List of rooms ids to retrieve.'), 83 | owner: Joi 84 | .number() 85 | .integer() 86 | .description('List rooms for owner id.') 87 | } 88 | }, 89 | plugins: { 90 | 'hapi-swagger': { 91 | responseMessages: [ 92 | { 'code': 404, 'message': 'Rooms Not Found' }, 93 | { 'code': 503, 'message': 'Service Unavailable' } 94 | ] 95 | } 96 | }, 97 | handler: ( request, reply ) => { 98 | rooms.list( request, result => { 99 | if ( result !== null ) { 100 | reply({ 101 | statusCode: 200, 102 | message: 'Rooms Found.', 103 | data: result 104 | }).code(200); 105 | } else if ( result === null ) { 106 | reply({ 107 | statusCode: 404, 108 | message: 'Rooms Not Found.', 109 | error: '404 error' 110 | }).code(404); 111 | } else { 112 | reply({ 113 | statusCode: 500, 114 | message: 'Something Bad Happened.', 115 | error: '500 error' 116 | }).code(500); 117 | } 118 | }); 119 | } 120 | } 121 | }, 122 | /* 123 | * Read messages from redis 124 | */ 125 | { 126 | method: 'GET', 127 | path: '/rooms/{id}/messages', 128 | config: { 129 | tags: [ 'api', 'chat', 'messages' ], 130 | description: 'Retrieve messages', 131 | validate: { 132 | params: { 133 | id: Joi.number() 134 | .integer() 135 | .required() 136 | .description('Room\'s id') 137 | } 138 | }, 139 | }, 140 | handler: ( request, reply ) => { 141 | rooms.readMessages( request, result => { 142 | if ( result !== null ) { 143 | reply({ 144 | statusCode: 200, 145 | message: 'Messages Retrieved Successfully', 146 | data: result 147 | }).code(200); 148 | } else { 149 | reply({ 150 | statusCode: 500, 151 | message: 'Something Bad Happened.', 152 | error: 'err' 153 | }).code(500); 154 | } 155 | }); 156 | } 157 | } 158 | ]; 159 | 160 | export { chatRoomController as default }; 161 | -------------------------------------------------------------------------------- /src/extensions/counterExtension/actions/index.js: -------------------------------------------------------------------------------- 1 | export const INCREMENT_COUNTER_EXTENSION = 'INCREMENT_COUNTER_EXTENSION'; 2 | export const DECREMENT_COUNTER_EXTENSION = 'DECREMENT_COUNTER_EXTENSION'; 3 | 4 | export function increment() { 5 | return { 6 | type: INCREMENT_COUNTER_EXTENSION 7 | }; 8 | } 9 | 10 | export function decrement() { 11 | return { 12 | type: DECREMENT_COUNTER_EXTENSION 13 | }; 14 | } 15 | 16 | export function incrementIfOdd() { 17 | return (dispatch, getState) => { 18 | const { counterExtension } = getState(); 19 | 20 | if (counterExtension % 2 === 0) { 21 | return; 22 | } 23 | 24 | dispatch(increment()); 25 | }; 26 | } 27 | 28 | export function incrementAsync(delay = 1000) { 29 | return dispatch => { 30 | setTimeout(() => { 31 | dispatch(increment()); 32 | }, delay); 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /src/extensions/counterExtension/components/Counter.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | 3 | class Counter extends Component { 4 | render() { 5 | const { increment, incrementIfOdd, incrementAsync, decrement, counterExtension } = this.props; 6 | return ( 7 |
8 | Clicked: { counterExtension } times 9 | { ' ' } 10 | 11 | { ' ' } 12 | 13 | { ' ' } 14 | 15 | { ' ' } 16 | 17 |
18 | ); 19 | } 20 | } 21 | 22 | Counter.propTypes = { 23 | increment: PropTypes.func.isRequired, 24 | incrementIfOdd: PropTypes.func.isRequired, 25 | incrementAsync: PropTypes.func.isRequired, 26 | decrement: PropTypes.func.isRequired, 27 | counterExtension: PropTypes.number 28 | }; 29 | 30 | export default Counter; 31 | -------------------------------------------------------------------------------- /src/extensions/counterExtension/containers/index.js: -------------------------------------------------------------------------------- 1 | import { bindActionCreators } from 'redux'; 2 | import { connect } from 'react-redux'; 3 | import Counter from '../components/Counter'; 4 | import * as CounterActions from '../actions'; 5 | 6 | const mapStateToProps = ( state ) => { 7 | return { 8 | counterExtension: state.counterExtension, 9 | routerState: state.router 10 | }; 11 | }; 12 | 13 | const mapDispatchToProps = ( dispatch ) => { 14 | return bindActionCreators( CounterActions, dispatch ); 15 | }; 16 | 17 | export default connect( mapStateToProps, mapDispatchToProps )( Counter ); 18 | -------------------------------------------------------------------------------- /src/extensions/counterExtension/index.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | name: 'Counter Extension', 3 | active: true 4 | }; 5 | 6 | const routes = { 7 | // TODO 8 | }; 9 | 10 | export default config; 11 | -------------------------------------------------------------------------------- /src/extensions/counterExtension/reducers/index.js: -------------------------------------------------------------------------------- 1 | const initialState = 0; 2 | 3 | const counterExtension = (state = initialState, action) => { 4 | switch (action.type) { 5 | case 'INCREMENT_COUNTER_EXTENSION': 6 | return state + 1; 7 | case 'DECREMENT_COUNTER_EXTENSION': 8 | return state - 1; 9 | default: 10 | return state; 11 | } 12 | }; 13 | 14 | export default counterExtension; 15 | -------------------------------------------------------------------------------- /src/extensions/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import EmptyComponent from '../components/EmptyComponent'; 3 | import { connect } from 'react-redux'; 4 | 5 | const mapStateToProps = state => { 6 | return { 7 | extensions: state.extensions.enabled 8 | }; 9 | }; 10 | 11 | class RenderExtension extends Component { 12 | 13 | isComponentActive = extensionName => { 14 | let isActive = false; 15 | for (const item of this.props.extensions) { 16 | if ( item.folderName === extensionName ) { 17 | isActive = true; 18 | break; 19 | } 20 | } 21 | return isActive; 22 | }; 23 | 24 | render() { 25 | const customRequire = extensionName => { 26 | let component = EmptyComponent; 27 | if (__SERVER__) { 28 | if (this.isComponentActive(extensionName)) { 29 | component = require('../extensions/' + extensionName + '/containers/index').default; 30 | } 31 | } else { 32 | const req = require.context('../extensions', true); 33 | if (this.isComponentActive(extensionName)) { 34 | component = req('./' + extensionName + '/containers/index').default; 35 | } 36 | } 37 | return component; 38 | }; 39 | 40 | const ExtensionComponent = customRequire(this.props.name); 41 | 42 | return ( 43 |
44 | 45 |
46 | ); 47 | } 48 | } 49 | 50 | RenderExtension.propTypes = { 51 | name: PropTypes.string, 52 | extensions: PropTypes.array.isRequired 53 | }; 54 | 55 | export default connect( mapStateToProps)( RenderExtension ); 56 | -------------------------------------------------------------------------------- /src/extensions/myCounterExample/actions/index.js: -------------------------------------------------------------------------------- 1 | export const INCREMENT_COUNTER_EXAMPLE = 'INCREMENT_COUNTER_EXAMPLE'; 2 | export const DECREMENT_COUNTER_EXAMPLE = 'DECREMENT_COUNTER_EXAMPLE'; 3 | 4 | export function increment() { 5 | return { 6 | type: INCREMENT_COUNTER_EXAMPLE 7 | }; 8 | } 9 | 10 | export function decrement() { 11 | return { 12 | type: DECREMENT_COUNTER_EXAMPLE 13 | }; 14 | } 15 | 16 | export function incrementIfOdd() { 17 | return (dispatch, getState) => { 18 | const { myCounterExample } = getState(); 19 | 20 | if (myCounterExample % 2 === 0) { 21 | return; 22 | } 23 | 24 | dispatch(increment()); 25 | }; 26 | } 27 | 28 | export function incrementAsync(delay = 1000) { 29 | return dispatch => { 30 | setTimeout(() => { 31 | dispatch(increment()); 32 | }, delay); 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /src/extensions/myCounterExample/components/MyCounter.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { StyleRoot } from 'radium'; 3 | // import Button from '../themes/default/Button'; 4 | import Theme from '../../../themes'; 5 | 6 | class Counter extends Component { 7 | render() { 8 | const { increment, incrementIfOdd, incrementAsync, decrement, myCounterExample } = this.props; 9 | return ( 10 | 11 | Clicked: { myCounterExample } times 12 | { ' ' } 13 | + 14 | { ' ' } 15 | - 16 | { ' ' } 17 | Increment if odd 18 | { ' ' } 19 | incrementAsync() }>Increment async 20 | 21 | ); 22 | } 23 | } 24 | 25 | Counter.propTypes = { 26 | increment: PropTypes.func.isRequired, 27 | incrementIfOdd: PropTypes.func.isRequired, 28 | incrementAsync: PropTypes.func.isRequired, 29 | decrement: PropTypes.func.isRequired, 30 | myCounterExample: PropTypes.number.isRequired 31 | }; 32 | 33 | export default Counter; 34 | -------------------------------------------------------------------------------- /src/extensions/myCounterExample/containers/index.js: -------------------------------------------------------------------------------- 1 | import { bindActionCreators } from 'redux'; 2 | import { connect } from 'react-redux'; 3 | import MyCounter from '../components/MyCounter'; 4 | import * as CounterActions from '../actions'; 5 | 6 | const mapStateToProps = ( state ) => { 7 | return { 8 | myCounterExample: state.myCounterExample, 9 | routerState: state.router 10 | }; 11 | }; 12 | 13 | const mapDispatchToProps = ( dispatch ) => { 14 | return bindActionCreators( CounterActions, dispatch ); 15 | }; 16 | 17 | export default connect( mapStateToProps, mapDispatchToProps )( MyCounter ); 18 | -------------------------------------------------------------------------------- /src/extensions/myCounterExample/index.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | active: true 3 | } 4 | 5 | export default config; 6 | -------------------------------------------------------------------------------- /src/extensions/myCounterExample/reducers/index.js: -------------------------------------------------------------------------------- 1 | const myCounterExample = (state = 0, action) => { 2 | switch (action.type) { 3 | case 'INCREMENT_COUNTER_EXAMPLE': 4 | return state + 1; 5 | case 'DECREMENT_COUNTER_EXAMPLE': 6 | return state - 1; 7 | default: 8 | return state; 9 | } 10 | }; 11 | 12 | export default myCounterExample; 13 | -------------------------------------------------------------------------------- /src/extensions/paypalPayments/actions/index.js: -------------------------------------------------------------------------------- 1 | export const LOAD_USER_PAYMENT = 'LOAD_USER_PAYMENT'; 2 | export const LOAD_USER_PAYMENT_SUCCESS = 'LOAD_USER_PAYMENT_SUCCESS'; 3 | export const LOAD_USER_PAYMENT_FAILURE = 'LOAD_USER_PAYMENT_FAILURE'; 4 | export const CREATE_PAYMENT = 'CREATE_PAYMENT'; 5 | export const CREATE_PAYMENT_SUCCESS = 'CREATE_PAYMENT_SUCCESS'; 6 | export const CREATE_PAYMENT_FAILURE = 'CREATE_PAYMENT_FAILURE'; 7 | export const EXECUTE_PAYMENT = 'EXECUTE_PAYMENT'; 8 | export const EXECUTE_PAYMENT_SUCCESS = 'EXECUTE_PAYMENT_SUCCESS'; 9 | export const EXECUTE_PAYMENT_FAILURE = 'EXECUTE_PAYMENT_FAILURE'; 10 | export const CREATE_PAYOUT = 'CREATE_PAYOUT'; 11 | export const CREATE_PAYOUT_SUCCESS = 'CREATE_PAYOUT_SUCCESS'; 12 | export const CREATE_PAYOUT_FAILURE = 'CREATE_PAYOUT_FAILURE'; 13 | export const UPDATE_USER_FUNDS = 'UPDATE_USER_FUNDS'; 14 | export const CLEAR_USER_PAYMENT_DATA = 'CLEAR_USER_PAYMENT_DATA'; 15 | 16 | export const loadUserPayment = ( user ) => { 17 | return { 18 | type: [ LOAD_USER_PAYMENT ], 19 | promise: ( client ) => client.get('/users/' + user.id + '/payments', { 20 | headers: { 21 | 'Accept': 'application/json', 22 | 'Content-Type': 'application/json', 23 | 'Authorization': user.accessToken 24 | } 25 | }) 26 | }; 27 | }; 28 | 29 | export const createPayment = ( user, amount ) => { 30 | return { 31 | type: [ CREATE_PAYMENT ], 32 | promise: ( client ) => client.post('/payments/create', { 33 | headers: { 34 | 'Accept': 'application/json', 35 | 'Content-Type': 'application/json', 36 | 'Authorization': user.accessToken 37 | }, 38 | data: { 39 | userId: parseInt(user.id, 10), 40 | amount: Number(amount) 41 | } 42 | }) 43 | }; 44 | }; 45 | 46 | export const executePayment = ( user, paymentId, payerID ) => { 47 | return { 48 | type: [ EXECUTE_PAYMENT ], 49 | promise: ( client ) => client.post('/payments/execute', { 50 | headers: { 51 | 'Accept': 'application/json', 52 | 'Content-Type': 'application/json', 53 | 'Authorization': user.accessToken 54 | }, 55 | data: { 56 | userId: parseInt(user.id, 10), 57 | paymentId, 58 | payerID 59 | } 60 | }) 61 | }; 62 | }; 63 | 64 | export const createPayout = ( user, amount ) => { 65 | return { 66 | type: [ CREATE_PAYOUT ], 67 | promise: ( client ) => client.post('/payments/payouts', { 68 | headers: { 69 | 'Accept': 'application/json', 70 | 'Content-Type': 'application/json', 71 | 'Authorization': user.accessToken 72 | }, 73 | data: { 74 | userId: parseInt(user.id, 10), 75 | amount: Number(amount) 76 | } 77 | }) 78 | }; 79 | }; 80 | 81 | export const updateUserFunds = funds => { 82 | return { 83 | type: UPDATE_USER_FUNDS, 84 | funds 85 | }; 86 | }; 87 | 88 | export const clearPaymentData = () => { 89 | return { 90 | type: CLEAR_USER_PAYMENT_DATA 91 | }; 92 | }; 93 | 94 | export const isUserPaymentLoaded = ( globalState ) => { 95 | return globalState.paypalPayments && globalState.paypalPayments.loaded; 96 | }; 97 | -------------------------------------------------------------------------------- /src/extensions/paypalPayments/components/CreatePayment.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import Theme from '../../../themes'; 3 | 4 | class CreatePayment extends Component { 5 | constructor( props ) { 6 | super( props ); 7 | this.state = { 8 | amount: 10, 9 | minimumAmount: 5, 10 | resultMessage: '' 11 | }; 12 | } 13 | handleAmountOnChange = event => { 14 | this.setState({ amount: event.target.value }); 15 | }; 16 | 17 | createPayment = () => { 18 | const { createPayment, user } = this.props; 19 | if ( this.state.amount < this.state.minimumAmount ) { 20 | return this.setState({ resultMessage: 'Amount must be greater than ' + this.state.minimumAmount }); 21 | } 22 | createPayment(user, this.state.amount).then((action) => { 23 | if (!action.error) { 24 | this.setState({ resultMessage: 'You are now being redirected to Paypal to confirm your payment.' }); 25 | window.location = action.result.data.approvalUrl; 26 | } else { 27 | this.setState({ resultMessage: 'Something went wrong.' }); 28 | } 29 | }); 30 | }; 31 | 32 | render() { 33 | return ( 34 |
35 | 41 | Create Payment 42 | { this.state.resultMessage } 43 |
44 | ); 45 | } 46 | } 47 | 48 | CreatePayment.propTypes = { 49 | createPayment: PropTypes.func.isRequired, 50 | user: PropTypes.object.isRequired 51 | }; 52 | 53 | export default CreatePayment; 54 | -------------------------------------------------------------------------------- /src/extensions/paypalPayments/components/CreatePayout.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import Theme from '../../../themes'; 3 | 4 | export default class CreatePayout extends Component { 5 | constructor( props ) { 6 | super( props ); 7 | this.state = { 8 | amount: 10, 9 | minimumAmount: 5, 10 | resultMessage: '' 11 | }; 12 | } 13 | handleAmountOnChange = event => { 14 | this.setState({ amount: event.target.value }); 15 | }; 16 | 17 | createPayment = () => { 18 | const { createPayout, user } = this.props; 19 | if ( this.state.amount < this.state.minimumAmount ) { 20 | return this.setState({ resultMessage: 'Amount must be greater than ' + this.state.minimumAmount }); 21 | } 22 | createPayout(user, this.state.amount).then((action) => { 23 | if (!action.error && action.result.data.status === 'completed') { 24 | this.setState({ resultMessage: 'Your Paypal Account has been accredited.' }); 25 | } else if (!action.error && action.result.data.status === 'unclaimed') { 26 | this.setState({ resultMessage: 'Your request could not be completed because your email is not registered at Paypal' }); 27 | } else if (!action.error && action.result.data.status === 'insufficient funds') { 28 | this.setState({ resultMessage: 'Your account does not have enough funds to complete the transaction.' }); 29 | } else { 30 | this.setState({ resultMessage: 'Something went wrong while processing payout.' }); 31 | } 32 | }); 33 | }; 34 | 35 | render() { 36 | return ( 37 |
38 | 43 | Request Payout 44 | { this.state.resultMessage } 45 |
46 | ); 47 | } 48 | } 49 | 50 | CreatePayout.propTypes = { 51 | createPayout: PropTypes.func.isRequired, 52 | user: PropTypes.object.isRequired 53 | }; 54 | -------------------------------------------------------------------------------- /src/extensions/paypalPayments/components/DisplayUserFunds.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | 3 | export default class DisplayUserFunds extends Component { 4 | render() { 5 | const { paypalPayments } = this.props; 6 | return ( 7 |
8 | USER FUNDS: { paypalPayments.user_payments.funds } 9 |
10 | ); 11 | } 12 | } 13 | 14 | DisplayUserFunds.propTypes = { 15 | paypalPayments: PropTypes.object.isRequired 16 | }; 17 | -------------------------------------------------------------------------------- /src/extensions/paypalPayments/components/ExecutePayment.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import Theme from '../../../themes'; 3 | 4 | // TODO translate 5 | export default class ExecutePayment extends Component { 6 | constructor( props ) { 7 | super( props ); 8 | this.state = { 9 | amountToBeAccredited: 0, 10 | paymentStatus: 'pending', 11 | resultMessage: '' 12 | }; 13 | } 14 | executePayment = () => { 15 | const { 16 | executePayment, 17 | payerID, 18 | paymentId, 19 | user 20 | } = this.props; 21 | executePayment(user, paymentId, payerID).then((action) => { 22 | if (!action.error && action.result.data.status === 'approved') { 23 | // Once the payment has been executed 24 | // and user account accredited, proceed. 25 | this.setState({ 26 | paymentStatus: action.result.data.status, 27 | resultMessage: 'Your account has been accredited.' 28 | }); 29 | } else if (action.error === 400) { 30 | this.setState({ 31 | resultMessage: 'The payment has already been processed.' 32 | }); 33 | } else if (action.error === 404) { 34 | this.setState({ 35 | resultMessage: 'The payment could not be executed, it does not exist.' 36 | }); 37 | } else { 38 | this.setState({ 39 | resultMessage: 'Something went wrong, the payment could not be executed.' 40 | }); 41 | } 42 | }); 43 | }; 44 | 45 | render() { 46 | const { params } = this.props; 47 | return ( 48 |
49 | Payment status: { this.state.paymentStatus } 50 |
51 | Your account will be accredited with the following amount: { params.amount } 52 |
53 | Execute Payment 54 |
55 | { this.state.resultMessage } 56 |
57 | ); 58 | } 59 | } 60 | 61 | ExecutePayment.propTypes = { 62 | executePayment: PropTypes.func.isRequired, 63 | params: PropTypes.object, 64 | payerID: PropTypes.string, 65 | paymentId: PropTypes.string, 66 | user: PropTypes.object.isRequired 67 | }; 68 | -------------------------------------------------------------------------------- /src/extensions/paypalPayments/containers/ApprovedPaymentContainer.js: -------------------------------------------------------------------------------- 1 | import { bindActionCreators } from 'redux'; 2 | import { connect } from 'react-redux'; 3 | import ExecutePayment from '../components/ExecutePayment'; 4 | import * as PaymentsActions from '../actions'; 5 | 6 | const mapStateToProps = ( state ) => { 7 | return { 8 | payments: state.paypalPayments, 9 | params: state.router.params, 10 | user: state.user.data 11 | }; 12 | }; 13 | 14 | const mapDispatchToProps = ( dispatch ) => { 15 | return bindActionCreators( PaymentsActions, dispatch ); 16 | }; 17 | 18 | export default connect( mapStateToProps, mapDispatchToProps )( ExecutePayment ); 19 | -------------------------------------------------------------------------------- /src/extensions/paypalPayments/containers/CreatePaymentContainer.js: -------------------------------------------------------------------------------- 1 | import { bindActionCreators } from 'redux'; 2 | import { connect } from 'react-redux'; 3 | import CreatePayment from '../components/CreatePayment'; 4 | import * as PaymentsActions from '../actions'; 5 | 6 | const mapStateToProps = ( state ) => { 7 | return { 8 | payments: state.paypalPayments, 9 | user: state.user.data 10 | }; 11 | }; 12 | 13 | const mapDispatchToProps = ( dispatch ) => { 14 | return bindActionCreators( PaymentsActions, dispatch ); 15 | }; 16 | 17 | export default connect( mapStateToProps, mapDispatchToProps )( CreatePayment ); 18 | -------------------------------------------------------------------------------- /src/extensions/paypalPayments/containers/CreatePayoutContainer.js: -------------------------------------------------------------------------------- 1 | import { bindActionCreators } from 'redux'; 2 | import { connect } from 'react-redux'; 3 | import CreatePayout from '../components/CreatePayout'; 4 | import * as PaymentsActions from '../actions'; 5 | 6 | const mapStateToProps = ( state ) => { 7 | return { 8 | payments: state.paypalPayments, 9 | user: state.user.data 10 | }; 11 | }; 12 | 13 | const mapDispatchToProps = ( dispatch ) => { 14 | return bindActionCreators( PaymentsActions, dispatch ); 15 | }; 16 | 17 | export default connect( mapStateToProps, mapDispatchToProps )( CreatePayout ); 18 | -------------------------------------------------------------------------------- /src/extensions/paypalPayments/containers/index.js: -------------------------------------------------------------------------------- 1 | import { bindActionCreators } from 'redux'; 2 | import { connect } from 'react-redux'; 3 | import DisplayUserFunds from '../components/DisplayUserFunds'; 4 | import * as PaymentsActions from '../actions'; 5 | 6 | const mapStateToProps = ( state ) => { 7 | return { 8 | paypalPayments: state.paypalPayments, 9 | user: state.user.data 10 | }; 11 | }; 12 | 13 | const mapDispatchToProps = ( dispatch ) => { 14 | return bindActionCreators( PaymentsActions, dispatch ); 15 | }; 16 | 17 | export default connect( mapStateToProps, mapDispatchToProps )( DisplayUserFunds ); 18 | -------------------------------------------------------------------------------- /src/extensions/paypalPayments/index.js: -------------------------------------------------------------------------------- 1 | // Extension name: Paypal Payments 2 | import { 3 | clearPaymentData, 4 | isUserPaymentLoaded as isLoaded, 5 | loadUserPayment 6 | } from './actions'; 7 | 8 | const config = { 9 | name: 'Paypal Payments', 10 | active: true, 11 | port: 5000, 12 | api: { 13 | host: 'https://api.sandbox.paypal.com', // SandBox Api 14 | // host: 'https://api.paypal.com', // Live Api 15 | port: '', 16 | client_id: '<-YOUR-PAYPAL-CLIENT-ID->', 17 | client_secret: '<-YOUR-PAYPAL-CLIENT-SECRET->', 18 | routes: { 19 | payment: '/v1/payments/payment', 20 | payouts: '/v1/payments/payouts', 21 | execute: '/execute/', 22 | oauth: '/v1/oauth2/token' 23 | } 24 | }, 25 | payouts: { 26 | emailSubject: 'You have a payment', 27 | batchMode: false // TODO 28 | } 29 | }; 30 | // indicate what data needs to be loaded 31 | // for this extension 32 | // TODO more documentation 33 | const preLoad = [ 34 | { 35 | isLoaded: isLoaded, 36 | function: loadUserPayment, 37 | // needs, indicate what type of reducer is needed 38 | needs: 'user', 39 | // scope, indicates the scope a user should have 40 | // in order to load this extenion's data 41 | scopes: [ 'user' ] 42 | } 43 | ]; 44 | // functions for clearing out any extension data 45 | // eg: when a user logs out 46 | const clearData = [ 47 | { 48 | isLoaded: isLoaded, 49 | function: clearPaymentData, 50 | needs: 'user', 51 | scopes: [ 'user' ] 52 | } 53 | ]; 54 | 55 | export { clearData }; 56 | export { preLoad }; 57 | 58 | export default config; 59 | -------------------------------------------------------------------------------- /src/extensions/paypalPayments/lang/es.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len,quotes */ 2 | const es = { 3 | "menuItem.paymentCreate": "Depositar", 4 | "menuItem.paymentPayout": "Retirar" 5 | }; 6 | 7 | export default es; 8 | -------------------------------------------------------------------------------- /src/extensions/paypalPayments/lang/index.js: -------------------------------------------------------------------------------- 1 | import { defineMessages } from 'react-intl'; 2 | import es from './es'; 3 | import it from './it'; 4 | const languages = [ 5 | { 6 | locale: 'es', 7 | file: es 8 | }, 9 | { 10 | locale: 'it', 11 | file: it 12 | } 13 | ]; 14 | 15 | const menu = [ 16 | { 17 | text: 'Deposit', 18 | to: '/payments/create', 19 | scopes: [ 'user' ], 20 | translated: 'menuPaymentCreate' 21 | }, 22 | { 23 | text: 'Payout', 24 | to: '/payments/payout', 25 | scopes: [ 'user' ], 26 | translated: 'menuPaymentPayout' 27 | } 28 | ]; 29 | 30 | const messages = defineMessages({ 31 | menuPaymentCreate: { 32 | id: 'menuItem.paymentCreate', 33 | description: 'Create a new payment button', 34 | defaultMessage: menu[0].text 35 | }, 36 | menuPaymentPayout: { 37 | id: 'menuItem.paymentPayout', 38 | description: 'Create a new payment payout button', 39 | defaultMessage: menu[1].text 40 | } 41 | }); 42 | 43 | export { menu }; 44 | export { messages }; 45 | export default languages; 46 | -------------------------------------------------------------------------------- /src/extensions/paypalPayments/lang/it.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len,quotes */ 2 | const it = { 3 | "menuItem.paymentCreate": "Depositare", 4 | "menuItem.paymentPayout": "Prelevare" 5 | }; 6 | 7 | export default it; 8 | -------------------------------------------------------------------------------- /src/extensions/paypalPayments/pages/approved.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { StyleRoot } from 'radium'; 3 | import Helmet from 'react-helmet'; 4 | import ExecutePayment from '../containers/ApprovedPaymentContainer'; 5 | // Check approved transaction and deposit credits into user's account 6 | export default class PaymentApproved extends Component { 7 | render() { 8 | const { paymentId, PayerID } = this.props.location.query; 9 | return ( 10 | 11 | 12 | 13 | 14 | ); 15 | } 16 | } 17 | 18 | PaymentApproved.propTypes = { 19 | paymentId: PropTypes.string, 20 | PayerID: PropTypes.string, 21 | location: PropTypes.object.isRequired 22 | }; 23 | -------------------------------------------------------------------------------- /src/extensions/paypalPayments/pages/cancel.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Helmet from 'react-helmet'; 3 | 4 | export default class PaymentCancelled extends React.Component { 5 | render() { 6 | return ( 7 |
8 | 9 |
10 | ); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/extensions/paypalPayments/pages/create.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyleRoot } from 'radium'; 3 | import Helmet from 'react-helmet'; 4 | import CreatePayment from '../containers/CreatePaymentContainer'; 5 | 6 | export default class CreatePaymentPage extends React.Component { 7 | render() { 8 | return ( 9 | 10 | 11 | 12 | 13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/extensions/paypalPayments/pages/index.js: -------------------------------------------------------------------------------- 1 | const pages = [ 2 | { 3 | path: 'payments/create', 4 | component: require('./create').default, 5 | scope: 'registered' // public, private, loggedOut 6 | }, 7 | { 8 | path: 'payments/payout', 9 | component: require('./payout').default, 10 | scope: 'registered' // public, private, loggedOut 11 | }, 12 | { 13 | path: 'payments/approved/amount/:amount', 14 | component: require('./approved').default, 15 | scope: 'registered' // public, private, loggedOut 16 | }, 17 | { 18 | path: 'payments/cancel', 19 | component: require('./cancel').default, 20 | scope: 'registered' // public, private, loggedOut 21 | } 22 | ]; 23 | 24 | export default pages; 25 | -------------------------------------------------------------------------------- /src/extensions/paypalPayments/pages/payout.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyleRoot } from 'radium'; 3 | import Helmet from 'react-helmet'; 4 | import CreatePayout from '../containers/CreatePayoutContainer'; 5 | 6 | export default class CreatePaymentPage extends React.Component { 7 | render() { 8 | return ( 9 | 10 | 11 | 12 | 13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/extensions/paypalPayments/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | LOAD_USER_PAYMENT_REQUEST, LOAD_USER_PAYMENT_SUCCESS, LOAD_USER_PAYMENT_FAILURE, 3 | CREATE_PAYMENT_REQUEST, CREATE_PAYMENT_SUCCESS, CREATE_PAYMENT_FAILURE, 4 | EXECUTE_PAYMENT_REQUEST, EXECUTE_PAYMENT_SUCCESS, EXECUTE_PAYMENT_FAILURE, 5 | CREATE_PAYOUT_REQUEST, CREATE_PAYOUT_SUCCESS, CREATE_PAYOUT_FAILURE, 6 | UPDATE_USER_FUNDS, 7 | CLEAR_USER_PAYMENT_DATA 8 | } from '../actions'; 9 | 10 | const initialState = { 11 | loaded: false, 12 | created: false, 13 | executed: false, 14 | user_payments: { 15 | funds: 0 16 | } 17 | }; 18 | 19 | const payments = (state = initialState, action) => { 20 | switch (action.type) { 21 | case LOAD_USER_PAYMENT_REQUEST: { 22 | return { 23 | ...state, 24 | loaded: false, 25 | loading: true, 26 | error: false 27 | }; 28 | } 29 | case LOAD_USER_PAYMENT_SUCCESS: { 30 | return { 31 | ...state, 32 | loaded: true, 33 | loading: false, 34 | user_payments: Object.assign( 35 | {}, 36 | state.user_payments, 37 | action.result.data.user 38 | ) 39 | }; 40 | } 41 | case LOAD_USER_PAYMENT_FAILURE: { 42 | return { 43 | ...state, 44 | loaded: false, 45 | loading: false, 46 | error: action.error.toString() 47 | }; 48 | } 49 | case CREATE_PAYMENT_REQUEST: { 50 | return { 51 | ...state, 52 | created: false, 53 | creating: true, 54 | error: false 55 | }; 56 | } 57 | case CREATE_PAYMENT_SUCCESS: { 58 | return { 59 | ...state, 60 | created: true, 61 | creating: false 62 | }; 63 | } 64 | case CREATE_PAYMENT_FAILURE: { 65 | return { 66 | ...state, 67 | created: false, 68 | creating: false, 69 | error: action.error.toString() 70 | }; 71 | } 72 | case EXECUTE_PAYMENT_REQUEST: { 73 | return { 74 | ...state, 75 | executed: false, 76 | executing: true, 77 | error: false 78 | }; 79 | } 80 | case EXECUTE_PAYMENT_SUCCESS: { 81 | return { 82 | ...state, 83 | executed: true, 84 | executing: false, 85 | user_payments: Object.assign({}, state.user_payments, action.result.data) 86 | }; 87 | } 88 | case EXECUTE_PAYMENT_FAILURE: { 89 | return { 90 | ...state, 91 | executed: false, 92 | executing: false, 93 | error: action.error.toString() 94 | }; 95 | } 96 | case CREATE_PAYOUT_REQUEST: { 97 | return { 98 | ...state, 99 | payoutCreated: false, 100 | payoutreating: true, 101 | error: false 102 | }; 103 | } 104 | case CREATE_PAYOUT_SUCCESS: { 105 | return { 106 | ...state, 107 | payoutCreated: true, 108 | payoutreating: false, 109 | user_payments: Object.assign({}, state.user_payments, action.result.data) 110 | }; 111 | } 112 | case CREATE_PAYOUT_FAILURE: { 113 | return { 114 | ...state, 115 | payoutCreated: false, 116 | payoutreating: false, 117 | error: action.error.toString() 118 | }; 119 | } 120 | case UPDATE_USER_FUNDS: { 121 | return { 122 | ...state, 123 | user_payments: { 124 | funds: action.funds 125 | } 126 | }; 127 | } 128 | case CLEAR_USER_PAYMENT_DATA: { 129 | return Object.assign({}, initialState); 130 | } 131 | default: 132 | return state; 133 | } 134 | }; 135 | 136 | export default payments; 137 | -------------------------------------------------------------------------------- /src/extensions/styleSwitcher/actions/index.js: -------------------------------------------------------------------------------- 1 | export const SWITCH_THEME = 'SWITCH_THEME'; 2 | 3 | const switchTheme = theme => { 4 | return { 5 | type: SWITCH_THEME, 6 | theme 7 | }; 8 | }; 9 | 10 | export { switchTheme }; 11 | -------------------------------------------------------------------------------- /src/extensions/styleSwitcher/components/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | 3 | class SwitchTheme extends Component { 4 | handleLocaleOnChange = event => { 5 | event.preventDefault(); 6 | this.props.switchTheme(event.target.value); 7 | }; 8 | 9 | render() { 10 | return ( 11 |
12 | style Switcher { ' ' } 13 | {/* TODO: Convert to array of available themes*/} 14 | 18 |
19 | ); 20 | } 21 | } 22 | 23 | SwitchTheme.propTypes = { 24 | currentTheme: PropTypes.string, 25 | switchTheme: PropTypes.func 26 | }; 27 | 28 | export default SwitchTheme; 29 | -------------------------------------------------------------------------------- /src/extensions/styleSwitcher/containers/index.js: -------------------------------------------------------------------------------- 1 | import { bindActionCreators } from 'redux'; 2 | import { connect } from 'react-redux'; 3 | import StyleSwitcher from '../components'; 4 | import * as ThemeActions from '../actions'; 5 | 6 | const mapStateToProps = ( state ) => { 7 | return { 8 | currentTheme: state.styleSwitcher.currentTheme 9 | }; 10 | }; 11 | 12 | const mapDispatchToProps = ( dispatch ) => { 13 | return bindActionCreators( ThemeActions, dispatch ); 14 | }; 15 | 16 | export default connect( mapStateToProps, mapDispatchToProps )( StyleSwitcher ); 17 | -------------------------------------------------------------------------------- /src/extensions/styleSwitcher/index.js: -------------------------------------------------------------------------------- 1 | // Extension name: Style Switcher 2 | const config = { 3 | name: 'Style Switcher', 4 | active: true 5 | }; 6 | 7 | const routes = { 8 | // TODO 9 | }; 10 | 11 | export default config; 12 | -------------------------------------------------------------------------------- /src/extensions/styleSwitcher/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { SWITCH_THEME } from '../actions'; 2 | 3 | const initialState = { currentTheme: 'default' }; 4 | 5 | const styleSwitcher = (state = initialState, action = {}) => { 6 | switch (action.type) { 7 | case SWITCH_THEME: { 8 | return Object.assign({}, state, { 9 | currentTheme: action.theme 10 | }); 11 | } 12 | default: 13 | return state; 14 | } 15 | }; 16 | 17 | export default styleSwitcher; 18 | -------------------------------------------------------------------------------- /src/globals.js: -------------------------------------------------------------------------------- 1 | import config from './config'; 2 | // Prevent issues with libraries using this var (see http://tinyurl.com/pcockwk) 3 | delete process.env.BROWSER; 4 | 5 | // Check enviroment for production 6 | const enviroment = process.env.NODE_ENV || 'development'; 7 | global.__DEVELOPMENT__ = enviroment === 'development'; 8 | global.__PRODUCTION__ = enviroment === 'production'; 9 | 10 | // Define defaults for development 11 | let defaultServerProtcol = 'http://'; 12 | let defaultServerHOST = 'localhost'; 13 | let defaultServerPORT = 3000; 14 | let defaultServerURI = config.server.deveplopmentUrl; 15 | let defaultWsHost = 'localhost'; 16 | let defaultWsPort = 4001; 17 | let defaultRedisHost = 'localhost'; 18 | let defaultRedisPort = 6379; 19 | let defaultRedisDbNumber = 1; 20 | // Define defaults for production 21 | 22 | if ( __PRODUCTION__ ) { 23 | defaultServerProtcol = config.server.protocol; 24 | defaultServerHOST = 'localhost'; 25 | defaultServerPORT = 8080; 26 | defaultServerURI = config.server.productionUrl; 27 | defaultWsHost = config.server.ws.host; 28 | defaultWsPort = config.server.ws.port; 29 | defaultRedisHost = 'localhost'; 30 | defaultRedisPort = 6379; 31 | defaultRedisDbNumber = 1; 32 | } 33 | 34 | // Define protocol (http or https) 35 | const PROTOCOL = defaultServerProtcol; 36 | // Define cloud server host and port (For deployments) eg: OpenShift, Heorku... 37 | const serverHOST = process.env.OPENSHIFT_NODEJS_IP; // REPLACE WITH YOUR SERVER's IP 38 | const serverPORT = process.env.OPENSHIFT_NODEJS_PORT; // REPLACE WITH YOUR SERVER's PORT 39 | // Define cloud server for websockets 40 | const wsHOST = process.env.OPENSHIFT_NODEJS_WS_HOST; // usually an URL to your site 41 | const wsPORT = process.env.OPENSHIFT_NODEJS_WS_PORT; // REPLACE WITH YOUR SERVER's WebScokets PORT 42 | // Define cloud server dabatase settings 43 | const redisHOST = process.env.OPENSHIFT_REDIS_HOST; 44 | const redisPORT = process.env.OPENSHIFT_REDIS_PORT; 45 | const redisDBNUMBER = process.env.OPENSHIFT_REDIS_DBNUMBER; 46 | 47 | // Define Global Variables 48 | global.PROTOCOL = PROTOCOL; 49 | // Define server host and port 50 | global.SERVER_HOST = serverHOST || process.env.HOST || defaultServerHOST; // Defaults to localhost for deveplopment 51 | global.SERVER_PORT = serverPORT || process.env.PORT || defaultServerPORT; 52 | global.SERVER_URI = defaultServerURI; 53 | // Define websockets host and port 54 | global.WS_HOST = wsHOST || process.env.WSHOST || defaultWsHost; 55 | global.WS_PORT = wsPORT || process.env.WSPORT || defaultWsPort; 56 | // Define database host and port (REDIS), and database number 57 | global.REDIS_HOST = redisHOST || process.env.REDISHOST || defaultRedisHost; 58 | global.REDIS_PORT = redisPORT || process.env.REDISPORT || defaultRedisPort; 59 | global.REDIS_DBNUMBER = redisDBNUMBER || process.env.REDISDBNUMBER || defaultRedisDbNumber; 60 | -------------------------------------------------------------------------------- /src/helpers/ApiClient.js: -------------------------------------------------------------------------------- 1 | import Promise from 'bluebird'; 2 | import fetch from 'isomorphic-fetch'; 3 | import { parseJSON } from './fetchTools'; 4 | import config from '../config'; 5 | 6 | const methods = [ 'get', 'post', 'put', 'patch', 'del' ]; 7 | 8 | const formatUrl = path => { 9 | const adjustedPath = path[0] !== '/' ? '/' + path : path; 10 | 11 | if (__SERVER__) { 12 | // Prepend host and port of the API server to the path. 13 | return PROTOCOL + 14 | SERVER_HOST + 15 | ':' + 16 | SERVER_PORT + 17 | config.api.routes.path + 18 | adjustedPath; 19 | } 20 | // Prepend `/api` to relative URL, to proxy to API server. 21 | return config.api.routes.path + adjustedPath; 22 | }; 23 | 24 | const attachQuery = ( url, query ) => { 25 | let urlResult = url; 26 | let concatParams = null; 27 | let count = 0; 28 | let and = ''; 29 | 30 | if ( typeof query !== 'undefined' ) { 31 | urlResult = url + '?'; 32 | 33 | for (const param in query) { 34 | and = count > 0 ? '&' : ''; 35 | if ( query.hasOwnProperty( param ) ) { 36 | concatParams = and + param + '=' + query[param]; 37 | } 38 | count++; 39 | } 40 | urlResult = urlResult + concatParams; 41 | } 42 | 43 | return urlResult; 44 | }; 45 | 46 | class _ApiClient { 47 | constructor(req) { 48 | methods.forEach(( method ) => 49 | this[method] = ( path, { headers, query, params, data } = {}) => new Promise(( resolve, reject ) => { 50 | let body = null; 51 | let content = ''; 52 | 53 | if (__SERVER__ && req.headers.cookie) { 54 | headers.Cookie = req.headers.cookie; 55 | } 56 | 57 | if (!__SERVER__ && typeof query !== 'undefined') { 58 | path = attachQuery(path, query); 59 | } 60 | 61 | 62 | if (!__SERVER__ && typeof data !== 'undefined') { 63 | body = JSON.stringify(data); 64 | } 65 | 66 | if ( body !== null ) { 67 | content = { 68 | credentials: 'same-origin', 69 | method, 70 | headers, 71 | body 72 | }; 73 | } else { 74 | content = { 75 | credentials: 'same-origin', 76 | method, 77 | headers 78 | }; 79 | } 80 | 81 | fetch(formatUrl(path), content) 82 | .then(parseJSON) 83 | .then( response => { 84 | if ( !response.error ) { 85 | resolve(response); 86 | } else { 87 | reject( new Error(response.error) ); 88 | } 89 | }); 90 | }) 91 | ); 92 | } 93 | } 94 | 95 | const ApiClient = _ApiClient; 96 | 97 | export default ApiClient; 98 | -------------------------------------------------------------------------------- /src/helpers/Html.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { renderToString } from 'react-dom/server'; 3 | import serialize from 'serialize-javascript'; 4 | import Helmet from 'react-helmet'; 5 | // Import assets 6 | const scripts = []; 7 | if (!__DEVELOPMENT__) { 8 | const webpackConfig = require('../../webpack/prod.config'); 9 | const stats = require('../../static/assets/webpack.stats.json'); 10 | const assets = stats.assetsByChunkName; 11 | for ( const key of Object.keys(assets) ) { 12 | if (assets[key].endsWith('.js')) { 13 | scripts.push(webpackConfig.output.publicPath + assets[key]); 14 | } 15 | } 16 | } else { 17 | const webpackConfig = require('../../webpack/dev.config'); 18 | scripts.push(webpackConfig.output.publicPath + 'bundle.js'); 19 | } 20 | // import DocumentMeta from 'react-document-meta'; 21 | 22 | /** 23 | * Wrapper component containing HTML metadata and boilerplate tags. 24 | * Used in server-side code only to wrap the string output of the 25 | * rendered route component. 26 | * 27 | * The only thing this component doesn't (and can't) include is the 28 | * HTML doctype declaration, which is added to the rendered output 29 | * by the server.js file. 30 | */ 31 | 32 | export default class Html extends Component { 33 | static propTypes = { 34 | component: PropTypes.node, 35 | store: PropTypes.object, 36 | initialState: PropTypes.object 37 | }; 38 | 39 | render() { 40 | const { component, store } = this.props; 41 | let componentHTML = ''; 42 | let head = ''; 43 | let base = ''; 44 | let title = ''; 45 | let meta = ''; 46 | let link = ''; 47 | let script = ''; 48 | if (typeof component !== 'undefined') { 49 | componentHTML = renderToString(component); 50 | head = Helmet.rewind(); 51 | } 52 | if (head) { 53 | base = head.base.toComponent(); 54 | title = head.title.toComponent(); 55 | meta = head.meta.toComponent(); 56 | link = head.link.toComponent(); 57 | script = head.script.toComponent(); 58 | } 59 | 60 | const initialState = store.getState(); 61 | return ( 62 | 63 | 64 | { base } 65 | { title } 66 | { meta } 67 | { link } 68 | { script } 69 | 70 | 71 |
76 |