diff --git a/.env b/.env index 610c409b2..2c85a0ee9 100644 --- a/.env +++ b/.env @@ -1,6 +1,9 @@ NODE_ENV=development PORT=8000 -API_URL=http://quran.com:3000 +API_URL=http://staging.quran.com:3000 +ONE_QURAN_URL=http://localhost:3030 SEGMENTS_KEY= SENTRY_KEY_CLIENT= SENTRY_KEY_SERVER= +# Quran.com - development app, no need to worry! +FACEBOOK_APP_ID=1599019233731707 diff --git a/.eslintignore b/.eslintignore index 1ee2668e7..e69de29bb 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1 +0,0 @@ -src/components/SurahInfo/htmls/* diff --git a/.eslintrc b/.eslintrc index a277bf4a8..58b1abbaf 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,39 +1,24 @@ { - "parser": "babel-eslint", "extends": "airbnb", - "env": { - "browser": true, - "node": true, - "mocha": true, - "es6": true - }, + "parser": "babel-eslint", "rules": { - "react/no-multi-comp": 0, - "import/default": 0, - "import/no-duplicates": 0, - "import/named": 0, - "import/namespace": 0, + "comma-dangle": 0, + "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }], + "import/no-extraneous-dependencies": 0, + "import/extensions": 0, "import/no-unresolved": 0, - "import/no-named-as-default": 2, - // Temporarirly disabled due to a possible bug in babel-eslint (todomvc example) - "block-scoped-var": 0, - // Temporarily disabled for test/* until babel/babel-eslint#33 is resolved - "padded-blocks": 0, - "comma-dangle": 0, // not sure why airbnb turned this on. gross! - "indent": [2, 2, {"SwitchCase": 1}], - "no-console": 0, - "no-alert": 0, - "object-curly-spacing": 0, - "no-case-declarations": 0 + "strict": 0 + }, + "ecmaFeatures": { + "classes": true, + "jsx": true }, "plugins": [ - "react", "import" + "react", + "mocha" ], "settings": { - "import/parser": "babel-eslint", - "import/resolve": { - moduleDirectory: ["node_modules", "src"] - } + "import/resolver": "webpack" }, "parserOptions":{ "ecmaFeatures": { @@ -52,6 +37,12 @@ Raven: true, mixpanel: true, "expect": true, - "browser": true + "browser": true, + "FB": true, + sinon: true + }, + "env": { + "mocha": true, + "amd": true } } diff --git a/.gitignore b/.gitignore index 135ab60a2..707bb3209 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ tests/functional/output/* test/functional/screenshots/* .ssh webpack-stats.debug.json +*.DS_Store diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 219a7a38c..e35fbfe63 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -75,15 +75,8 @@ Pull requests are the greatest contributions, so be sure they are focused in sco 6. Now [open a pull request] with a clear title and description. -## Sever-side integration -Unless you have the backend API running locally, you will need to update the `API_URL`, in `development.env` file, from `localhost` to `api.quran.com`. Leave the port number same. - -To start the app, run `npm run dev` which will run both the server and the client (webpack) to compile upon edits. Go to http://localhost:8001 in your browser, not 8000 (that is just the express server). - -If you experience an issue, check the [contributing] guidelines. [upstream]: https://help.github.com/articles/syncing-a-fork/ -[contributing]: https://guides.github.com/activities/contributing-to-open-source/ [already been reported]: https://github.com/quran/quran.com-frontend/issues [fork this project]: https://github.com/quran/quran.com-frontend/fork [open a pull request]: https://help.github.com/articles/using-pull-requests/ diff --git a/Dockerfile b/Dockerfile index e3f6568dc..a43d27e73 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,6 +4,9 @@ ENV NODE_ENV production ENV API_URL http://api.quran.com:3000 ENV SENTRY_KEY_CLIENT https://44c105328ae544ae9928f9eb74b40061@app.getsentry.com/80639 ENV SENTRY_KEY_SERVER https://44c105328ae544ae9928f9eb74b40061:41ca814d33124e04ab450104c3938cb1@app.getsentry.com/80639 +# It's okay because it's only the APP ID +ENV FACEBOOK_APP_ID 1557596491207315 +ENV ONE_QURAN_URL https://one.quran.com ENV PORT 8000 ENV NODE_PATH "./src" @@ -26,6 +29,7 @@ RUN cp -a /tmp/node_modules /quran WORKDIR /quran ADD . /quran/ + RUN npm run build:client RUN npm run build:server diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 000000000..d932105ee --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 Quran.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 71c29abb7..3a973e5f6 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,35 @@ - # Quran.com -This is the project soon to be the Quran.com facing site. This is built in +This project is the frontend for Quran.com. It is built using [Reactjs] + [Redux] + [Expressjs] + [Webpack]. It is isomorphic (javascript shared between both the server and the client) for SEO reasons. -[![Stories Ready](https://badge.waffle.io/quran/quran.com-frontend.svg?label=ready&title=Ready)](http://waffle.io/quran/quran.com-frontend) -[![Stories In Progress](https://badge.waffle.io/quran/quran.com-frontend.svg?label=in%20progress&title=In%20Progress)](http://waffle.io/quran/quran.com-frontend) -[![Stories In Review](https://badge.waffle.io/quran/quran.com-frontend.svg?label=in%20review&title=In%20Review)](http://waffle.io/quran/quran.com-frontend) - - -[![Dependency Status](https://david-dm.org/quran/quran.com-frontend.svg)](https://david-dm.org/quran/quran.com-frontend) [![devDependency Status](https://david-dm.org/quran/quran.com-frontend/dev-status.svg)](https://david-dm.org/quran/quran.com-frontend#info=devDependencies) [![Code Climate](https://codeclimate.com/github/quran/quran.com-frontend.png)](https://codeclimate.com/github/quran/quran.com-frontend) ## How to contribute -We trust that you will not copy this idea/project, this is at the end for the sake of Allah and we all have good intentions while working with this project. But We must stress that copying the code/project is unacceptable. +We trust that you will not copy this idea/project, this is at the end for the sake of Allah and we all have good intentions while working with this project. But we must stress that copying the code/project is unacceptable. Read the [contributing] section before creating an issue. -## Server-Side Integration -Unless you have the backend API running locally, you will need to update the `API_URL`, in `development.env` file, from `localhost` to `api.quran.com`. Leave the port number same. - -To start the app, run `npm run dev` which will run both the server and the client (webpack) to compile upon edits. Go to http://localhost:8001 in your browser, not 8000 (that is just the express server). +## Running the app locally +- Ensure you have [nodejs] installed +- Get the source by running `git clone https://github.com/quran/quran.com-frontend/` or creating a [fork] +- Run `npm install` to do first time installation of all dependencies +- Run `npm run dev` to start the dev server +- Open `http://localhost:8000` in your browser to see the app. +## Staging +To see the app with the latest changes, see the [staging] site. Production releases are made periodically when staging is stable and well tested. ## Backend -Current at: https://github.com/quran/quran-api-rails -DB is private, message me for access. +The API source is at https://github.com/quran/quran-api-rails + +DB is private, message @mmahalwy for access. + +The dev server uses the staging API by default. If you want to use a local API server, follow the instructions in the API repo and run the server locally then update the API_URL field in app.json to point to the local address. +## Slack +Signup at https://quranslack.herokuapp.com to be added to the Slack group ## Design We currently use InvisionApp. Again, contact me if you'd like access to it. @@ -44,4 +46,7 @@ analyze-bundle-size bundle-stats.json [Redux]: http://redux.js.org/ [Expressjs]: http://expressjs.com/en/starter/hello-world.html [Webpack]: http://webpack.github.io/docs/what-is-webpack.html +[nodejs]: https://nodejs.org/en/ [contributing]: CONTRIBUTING.md +[fork]: https://help.github.com/articles/fork-a-repo/ +[staging]: https://staging.quran.com diff --git a/karma.conf.js b/karma.conf.js index d21d97f4a..ac9b1aa40 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -13,17 +13,19 @@ module.exports = function(config) { 'karma-sinon', 'karma-webpack', 'karma-chrome-launcher', - 'karma-phantomjs-launcher' + 'karma-phantomjs-launcher', + 'karma-intl-shim' ], // frameworks to use // available frameworks: https://npmjs.org/browse/keyword/karma-adapter - frameworks: ['mocha', 'chai-sinon', 'sinon'], + frameworks: ['mocha', 'chai-sinon', 'sinon', 'intl-shim'], // list of files / patterns to load in the browser files: [ './node_modules/phantomjs-polyfill/bind-polyfill.js', './tests/polyfill/Event.js', + './node_modules/Intl/locale-data/jsonp/en-US.js', {pattern: 'static/images/*', watched: false, included: false, served: true}, // Actual tests here diff --git a/package.json b/package.json index 242a0cc72..0d4313466 100644 --- a/package.json +++ b/package.json @@ -5,14 +5,15 @@ "repository": "https://github.com/quran/quran.com-frontend", "scripts": { "test": "npm run test:dev:unit", - "test:ci:unit": "karma start --browsers PhantomJS --single-run; npm run test:ci:lint", + "test:ci:unit": "karma start --browsers PhantomJS --single-run", + "pretest:ci:unit": "npm run test:ci:lint", "test:ci:functional": "BROWSER=phantomjs ./tests/functional/test.sh start-ci", "posttest:ci:functional": "./tests/functional/test.sh stop", "test:dev:unit": "karma start", - "test:ci:lint": "eslint ./src/**/*.js", + "test:ci:lint": "./node_modules/eslint/bin/eslint.js ./src", "test:dev:functional": "BROWSER=chrome ./tests/functional/test.sh start", "posttest:dev:functional": "./tests/functional/test.sh stop", - "test:dev:lint": "eslint ./src/scripts/**/*.js", + "test:dev:lint": "./node_modules/eslint/bin/eslint.js ./src", "test:stylelint": "stylelint './src/**/*.scss' --config ./webpack/.stylelintrc", "dev": "env NODE_PATH='./src' PORT=8000 UV_THREADPOOL_SIZE=100 node ./webpack/webpack-dev-server.js & env NODE_PATH='./src' PORT=8000 node ./bin/server.js", "start": "NODE_PATH='src' node ./start", @@ -38,6 +39,7 @@ "babel-plugin-transform-react-constant-elements": "6.9.1", "babel-plugin-transform-react-display-name": "6.8.0", "babel-plugin-transform-react-inline-elements": "6.8.0", + "babel-plugin-transform-react-remove-prop-types": "0.2.11", "babel-plugin-transform-runtime": "6.12.0", "babel-plugin-typecheck": "3.9.0", "babel-polyfill": "6.13.0", @@ -54,59 +56,65 @@ "cache-manager": "1.5.0", "clean-webpack-plugin": "0.1.10", "compression": "1.6.2", + "compression-webpack-plugin": "0.3.2", "cookie-parser": "1.4.3", - "copy-to-clipboard": "1.1.1", + "copy-to-clipboard": "3.0.5", "cors": "2.7.1", - "crypto-js": "3.1.6", "css-loader": "0.23.1", "debug": "2.2.0", - "dotenv": "1.2.0", + "dotenv": "2.0.0", "errorhandler": "1.4.3", "express": "4.14.0", "express-state": "1.4.0", - "express-useragent": "0.2.4", + "express-useragent": "1.0.4", "extract-text-webpack-plugin": "2.0.0-beta.3", "file-loader": "0.8.5", "fontfaceobserver": "1.7.3", + "history": "^3.0.0", "html-webpack-plugin": "1.7.0", "http-proxy": "1.14.0", - "humps": "1.1.0", + "humps": "2.0.0", "imports-loader": "0.6.5", - "jquery": "2.2.4", "json-loader": "0.5.4", "morgan": "1.7.0", - "node-sass": "3.8.0", - "normalizr": "2.2.1", + "node-sass": "4.1.1", + "normalizr": "3.0.2", "pretty-error": "2.0.0", "promise": "7.1.1", "proxy-middleware": "0.14.0", "qs": "6.2.1", - "raven": "0.11.0", + "raven": "1.1.1", "raw-loader": "0.5.1", - "react": "0.14.8", - "react-bootstrap": "0.29.5", - "react-cookie": "0.3.4", - "react-dom": "0.14.8", + "react": "15.4.1", + "react-a11y": "0.3.3", + "react-addons-create-fragment": "15.4.1", + "react-bootstrap": "0.30.7", + "react-cookie": "1.0.4", + "react-dom": "15.4.1", "react-helmet": "3.1.0", + "react-inlinesvg": "0.5.4", + "react-intl": "2.1.5", "react-metrics": "1.2.1", - "react-paginate": "0.4.3", - "react-redux": "4.4.5", - "react-router": "2.6.1", + "react-paginate": "4.1.0", + "react-redux": "5.0.1", + "react-router": "3.0.0", "react-router-bootstrap": "0.20.1", - "react-router-redux": "4.0.5", + "react-router-redux": "4.0.7", "react-router-scroll": "0.2.1", "react-scroll": "1.2.0", + "react-share": "1.11.0", + "react-sidebar": "2.2.1", "redux": "3.5.2", - "redux-connect": "2.4.0", + "redux-connect": "5.0.0", "reselect": "2.5.3", "resolve-url": "0.2.1", - "sass-loader": "2.0.1", + "sass-loader": "4.1.1", "serialize-javascript": "1.3.0", "serve-favicon": "2.3.0", "sitemap": "1.8.1", "strip-loader": "0.1.2", "style-loader": "0.13.1", - "superagent": "1.8.4", + "superagent": "3.3.1", "url": "0.11.0", "url-loader": "0.5.7", "webpack": "2.1.0-beta.20", @@ -114,27 +122,29 @@ "winston": "1.1.2" }, "devDependencies": { - "babel-eslint": "6.0.4", + "babel-eslint": "7.1.1", "babel-plugin-react-transform": "2.0.2", "babel-preset-react-hmre": "1.1.1", "chai": "3.0.0", "chromedriver": "2.22.2", "del": "2.0.2", "enzyme": "2.2.0", - "eslint": "2.13.0", - "eslint-config-airbnb": "9.0.1", + "eslint": "3.12.2", + "eslint-config-airbnb": "13.0.0", "eslint-loader": "1.3.0", - "eslint-plugin-import": "1.8.1", - "eslint-plugin-jsx-a11y": "1.5.3", - "eslint-plugin-react": "5.2.2", + "eslint-plugin-import": "2.2.0", + "eslint-plugin-jsx-a11y": "2.2.3", + "eslint-plugin-mocha": "4.8.0", + "eslint-plugin-react": "6.8.0", "jscs": "2.1.1", - "karma": "1.2.0", + "karma": "1.3.0", "karma-chai": "0.1.0", "karma-chai-sinon": "0.1.5", "karma-chrome-launcher": "0.2.0", + "karma-intl-shim": "1.0.3", "karma-junit-reporter": "0.3.4", "karma-mocha": "0.2.0", - "karma-phantomjs-launcher": "~0.2.1", + "karma-phantomjs-launcher": "~1.0.2", "karma-script-launcher": "~0.1.0", "karma-sinon": "1.0.4", "karma-sourcemap-loader": "0.3.7", @@ -146,7 +156,7 @@ "phantomjs-polyfill": "0.0.1", "piping": "0.3.0", "pre-commit": "1.1.3", - "react-addons-test-utils": "0.14.7", + "react-addons-test-utils": "15.4.1", "react-transform-catch-errors": "1.0.0", "react-transform-hmr": "1.0.1", "redbox-react": "1.1.1", @@ -161,6 +171,7 @@ "wdio-mocha-framework": "0.3.7", "wdio-spec-reporter": "0.0.3", "webdriverio": "4.2.1", + "webpack-bundle-analyzer": "2.2.1", "webpack-dev-server": "1.6.5", "webpack-hot-middleware": "2.12.2" }, diff --git a/src/client.js b/src/client.js index 6ee43334d..cb2f06a95 100644 --- a/src/client.js +++ b/src/client.js @@ -12,6 +12,7 @@ import applyRouterMiddleware from 'react-router/lib/applyRouterMiddleware'; import useScroll from 'react-router-scroll'; import { ReduxAsyncConnect } from 'redux-connect'; import { syncHistoryWithStore } from 'react-router-redux'; +import { IntlProvider } from 'react-intl'; import debug from 'debug'; @@ -19,6 +20,7 @@ import config from './config'; import ApiClient from './helpers/ApiClient'; import createStore from './redux/create'; import routes from './routes'; +import getLocalMessages from './helpers/setLocal'; const client = new ApiClient(); const store = createStore(browserHistory, client, window.reduxData); @@ -27,7 +29,7 @@ const history = syncHistoryWithStore(browserHistory, store); try { Raven.config(config.sentryClient).install(); } catch (error) { - console.log(error); + debug('client', error); } window.quranDebug = debug; @@ -38,17 +40,19 @@ window.clearCookies = () => { reactCookie.remove('content'); reactCookie.remove('audio'); reactCookie.remove('isFirstTime'); + reactCookie.remove('currentLocale'); + reactCookie.remove('smartbanner-closed'); + reactCookie.remove('smartbanner-installed'); }; -match({ history, routes: routes() }, (error, redirectLocation, renderProps) => { +match({ history, routes: routes(store) }, (error, redirectLocation, renderProps) => { const component = ( ( + render={props => ( !item.deferred} + helpers={{ client }} render={applyRouterMiddleware(useScroll())} /> )} @@ -60,9 +64,11 @@ match({ history, routes: routes() }, (error, redirectLocation, renderProps) => { debug('client', 'React Rendering'); ReactDOM.render( - - {component} - , mountNode, () => { + + + {component} + + , mountNode, () => { debug('client', 'React Rendered'); } ); diff --git a/src/components/Audioplayer/RepeatDropdown/index.js b/src/components/Audioplayer/RepeatDropdown/index.js index 345d32ddf..d4f0379d7 100644 --- a/src/components/Audioplayer/RepeatDropdown/index.js +++ b/src/components/Audioplayer/RepeatDropdown/index.js @@ -4,23 +4,27 @@ import Popover from 'react-bootstrap/lib/Popover'; import Nav from 'react-bootstrap/lib/Nav'; import NavItem from 'react-bootstrap/lib/NavItem'; import FormControl from 'react-bootstrap/lib/FormControl'; -import Row from 'react-bootstrap/lib/Row'; import Col from 'react-bootstrap/lib/Col'; +import { intlShape, injectIntl } from 'react-intl'; import SwitchToggle from 'components/SwitchToggle'; +import LocaleFormattedMessage from 'components/LocaleFormattedMessage'; + +import surahType from 'types/surahType'; const style = require('../style.scss'); -export default class RepeatButton extends Component { +class RepeatButton extends Component { static propTypes = { - surah: PropTypes.object.isRequired, + surah: surahType, repeat: PropTypes.shape({ from: PropTypes.number, to: PropTypes.number, times: PropTypes.number }).isRequired, setRepeat: PropTypes.func.isRequired, - current: PropTypes.number.isRequired + current: PropTypes.number.isRequired, + intl: intlShape.isRequired }; handleToggle = () => { @@ -58,14 +62,18 @@ export default class RepeatButton extends Component { const array = Array(surah.ayat).join().split(','); return ( - - From - To:
-