From 6867760099eca0f8b6366c1457fcafa957b735b7 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 10 Nov 2016 17:14:33 -0700 Subject: [PATCH 01/86] Initial pass at adding middleware for users --- Dockerfile | 1 + README.md | 10 ++++ app.js | 54 +++++++++++++++++++++- docker-compose.yml | 10 ++++ middleware/authorization.js | 53 ++++++++++++++++++++++ mongoose.js | 16 +++---- package.json | 24 ++++------ passport.js | 83 ++++++++++++++++++++++++++++++++++ redis.js | 43 ++++++++++++++++++ routes/api/auth/index.js | 43 ++++++++++++++++++ routes/api/index.js | 1 + routes/index.js | 2 +- tests/routes/api/auth/index.js | 39 ++++++++++++++++ views/admin.ejs~HEAD | 22 --------- 14 files changed, 352 insertions(+), 49 deletions(-) create mode 100644 middleware/authorization.js create mode 100644 passport.js create mode 100644 redis.js create mode 100644 routes/api/auth/index.js create mode 100644 tests/routes/api/auth/index.js delete mode 100644 views/admin.ejs~HEAD diff --git a/Dockerfile b/Dockerfile index 3be5916a8..6776e2f04 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,6 +5,7 @@ RUN mkdir -p /usr/src/app WORKDIR /usr/src/app # Setup the environment +ENV NODE_ENV production ENV PATH /usr/src/app/bin:$PATH ENV TALK_PORT 5000 EXPOSE 5000 diff --git a/README.md b/README.md index f4d8b31de..ffc1f3077 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,16 @@ Run it once to install the dependencies. `npm start` Runs Talk. +### Configuration + +The Talk application requires specific configuration options to be available +inside the environment in order to run, those variables are listed here: + +- `TALK_SESSION_SECRET` (*required*) - +- `TALK_FACEBOOK_APP_ID` (*required*) - +- `TALK_FACEBOOK_APP_SECRET` (*required*) - +- `TALK_ROOT_URL` (*required*) - Root url of the installed application externally available in the format: `://` without the path. + ### Running with Docker Make sure you have Docker running first and then run `docker-compose up -d` diff --git a/app.js b/app.js index 22f343221..ed928622c 100644 --- a/app.js +++ b/app.js @@ -2,6 +2,11 @@ const express = require('express'); const bodyParser = require('body-parser'); const morgan = require('morgan'); const path = require('path'); +const helmet = require('helmet'); +const passport = require('./passport'); +const session = require('express-session'); +const RedisStore = require('connect-redis')(session); +const redis = require('./redis'); const app = express(); @@ -12,12 +17,57 @@ if (app.get('env') !== 'test') { app.use(morgan('dev')); } +//============================================================================== +// APP MIDDLEWARE +//============================================================================== + +app.set('trust proxy', 'loopback'); +app.use(helmet()); app.use(bodyParser.json()); +app.use('/client', express.static(path.join(__dirname, 'dist'))); app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'ejs'); -// Routes. -app.use('/client', express.static(path.join(__dirname, 'dist'))); +//============================================================================== +// SESSION MIDDLEWARE +//============================================================================== + +const session_opts = { + secret: process.env.TALK_SESSION_SECRET, + httpOnly: true, + rolling: true, + saveUninitialized: false, + resave: false, + cookie: { + secure: false, + maxAge: 18000000, // 30 minutes for expiry. + }, + store: new RedisStore({ + ttl: 1800, + client: redis, + }) +}; + +if (app.get('env') === 'production') { + + // Enable the secure cookie when we are in production mode. + session_opts.cookie.secure = true; +} + +app.use(session(session_opts)); + +//============================================================================== +// PASSPORT MIDDLEWARE +//============================================================================== + +// Setup the PassportJS Middleware. +app.use(passport.initialize()); +app.use(passport.session()); + +//============================================================================== +// ROUTES +//============================================================================== + app.use('/', require('./routes')); //============================================================================== diff --git a/docker-compose.yml b/docker-compose.yml index ecfac00bc..d7a180277 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,8 +10,10 @@ services: environment: - "TALK_PORT=5000" - "TALK_MONGO_URL=mongodb://mongo" + - "TALK_REDIS_URL=redis://redis" depends_on: - mongo + - redis mongo: image: mongo:3.2 @@ -19,6 +21,14 @@ services: volumes: - mongo:/data/db + redis: + image: redis:3.2-alpine + restart: always + volumes: + - redis:/data + volumes: mongo: external: false + redis: + external: false diff --git a/middleware/authorization.js b/middleware/authorization.js new file mode 100644 index 000000000..238219a06 --- /dev/null +++ b/middleware/authorization.js @@ -0,0 +1,53 @@ +/** + * authorization contains the references to the authorization middleware. + * @type {Object} + */ +const authorization = module.exports = {}; + +const debug = require('debug')('talk:middleware:authorization'); + +/** + * ErrNotAuthorized is an error that is returned in the event an operation is + * deemed not authorized. + * @type {Error} + */ +const ErrNotAuthorized = new Error('not authorized'); +ErrNotAuthorized.status = 401; + +// Add the ErrNotAuthorized error to the authorization object to be exported. +authorization.ErrNotAuthorized = ErrNotAuthorized; + +/** + * has returns true if the user has all the roles specified, otherwise it will + * return false. + * @param {Object} user the user to check for roles + * @param {Array} roles all the roles that a user must have + * @return {Boolean} true if the user has all the roles required, false + * otherwise + */ +authorization.has = (user, ...roles) => roles.every((role) => user.roles.indexOf(role) >= 0); + +/** + * needed is a connect middleware layer that ensures that all requests coming + * here are both authenticated and match a set of roles required to continue. + * @param {Array} roles all the roles that a user must have + * @return {Callback} connect middleware + */ +authorization.needed = (...roles) => (req, res, next) => { + // All routes that are wrapepd with this middleware actually require a role. + if (!req.user) { + debug(`No user on request, returning with ${ErrNotAuthorized}`); + return next(ErrNotAuthorized); + } + + // Check to see if the current user has all the roles requested for the given + // array of roles requested, if one is not on the user, then this will + // evaluate to true. + if (!authorization.has(req.user, ...roles)) { + debug('User does not have all the required roles to access this page'); + return next(ErrNotAuthorized); + } + + // Looks like they're allowed! + return next(); +}; diff --git a/mongoose.js b/mongoose.js index 9062816e6..712b2fcb0 100644 --- a/mongoose.js +++ b/mongoose.js @@ -10,16 +10,12 @@ if (enabled('talk:db')) { mongoose.set('debug', true); } -try { - mongoose.connect(url, (err) => { - if (err) { - throw err; - } +mongoose.connect(url, (err) => { + if (err) { + throw err; + } - debug('Connected to MongoDB!'); - }); -} catch (err) { - console.error('Cannot stablish a connection with MongoDB', err); -} + debug('connection established'); +}); module.exports = mongoose; diff --git a/package.json b/package.json index 207724d61..0de0b8607 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,6 @@ "build-watch": "webpack --config webpack.config.dev.js --watch", "lint": "eslint bin/* .", "lint-fix": "eslint . --fix", - "pretest": "npm install", "test": "mocha --compilers js:babel-core/register --recursive tests", "test-watch": "mocha --compilers js:babel-core/register --recursive -w tests", "embed-start": "npm run build && ./bin/www" @@ -17,13 +16,8 @@ "config": { "pre-git": { "commit-msg": [], - "pre-commit": [ - "npm run lint", - "npm test" - ], - "pre-push": [ - "npm test" - ], + "pre-commit": ["npm run lint", "npm test"], + "pre-push": ["npm test"], "post-commit": [], "post-merge": [] } @@ -32,12 +26,7 @@ "type": "git", "url": "git+https://github.com/coralproject/talk.git" }, - "keywords": [ - "talk", - "coral", - "coralproject", - "ask" - ], + "keywords": ["talk", "coral", "coralproject", "ask"], "author": "", "license": "Apache-2.0", "bugs": { @@ -48,12 +37,19 @@ "bcrypt": "^0.8.7", "body-parser": "^1.15.2", "commander": "^2.9.0", + "connect-redis": "^3.1.0", "debug": "^2.2.0", "ejs": "^2.5.2", "express": "^4.14.0", + "express-session": "^1.14.2", + "helmet": "^3.1.0", "mongoose": "^4.6.5", "morgan": "^1.7.0", + "passport": "^0.3.2", + "passport-facebook": "^2.1.1", + "passport-local": "^1.0.0", "prompt": "^1.0.0", + "redis": "^2.6.3", "uuid": "^2.0.3" }, "devDependencies": { diff --git a/passport.js b/passport.js new file mode 100644 index 000000000..ae4776894 --- /dev/null +++ b/passport.js @@ -0,0 +1,83 @@ +const passport = require('passport'); +const User = require('./models/user'); +const LocalStrategy = require('passport-local').Strategy; +const FacebookStrategy = require('passport-facebook').Strategy; + +//============================================================================== +// SESSION SERIALIZATION +//============================================================================== + +passport.serializeUser((user, done) => { + done(null, user.id); +}); + +passport.deserializeUser((id, done) => { + User + .findById(id) + .then((user) => { + done(null, user); + }) + .catch((err) => { + done(err); + }); +}); + +/** + * Validates that a user is allowed to login. + * @param {User} user the user to be validated + * @param {Function} done the callback for the validation + */ +function ValidateUserLogin(user, done) { + if (!user) { + return done(new Error('user not found')); + } + + if (user.disabled) { + return done(null, false, {message: 'Account disabled'}); + } + + return done(null, user); +} + +//============================================================================== +// STRATEGIES +//============================================================================== + +passport.use(new LocalStrategy({ + usernameField: 'email', + passwordField: 'password' +}, (email, password, done) => { + User + .findLocalUser(email, password) + .then((user) => { + if (!user) { + return done(null, false, {message: 'Incorrect email/password combination'}); + } + + return ValidateUserLogin(user, done); + }) + .catch((err) => { + done(err); + }); +})); + +if (process.env.TALK_FACEBOOK_APP_ID && process.env.TALK_FACEBOOK_APP_SECRET && process.env.TALK_ROOT_URL) { + passport.use(new FacebookStrategy({ + clientID: process.env.TALK_FACEBOOK_APP_ID, + clientSecret: process.env.TALK_FACEBOOK_APP_SECRET, + callbackURL: `${process.env.TALK_ROOT_URL}/connect/facebook/callback` + }, (accessToken, refreshToken, profile, done) => { + User + .findOrCreateExternalUser(profile) + .then((user) => + ValidateUserLogin(user, done) + ) + .catch((err) => { + done(err); + }); + })); +} else { + console.error('Facebook cannot be enabled, missing one of TALK_FACEBOOK_APP_ID, TALK_FACEBOOK_APP_SECRET, TALK_ROOT_URL'); +} + +module.exports = passport; diff --git a/redis.js b/redis.js new file mode 100644 index 000000000..0f2f211f9 --- /dev/null +++ b/redis.js @@ -0,0 +1,43 @@ +const redis = require('redis'); +const debug = require('debug')('talk:redis'); +const url = process.env.TALK_REDIS_URL || 'redis://localhost'; + +const client = redis.createClient(url, { + retry_strategy: function(options) { + if (options.error.code === 'ECONNREFUSED') { + + // End reconnecting on a specific error and flush all commands with a individual error + return new Error('The server refused the connection'); + } + if (options.total_retry_time > 1000 * 60 * 60) { + + // End reconnecting after a specific timeout and flush all commands with a individual error + return new Error('Retry time exhausted'); + } + + if (options.times_connected > 10) { + + // End reconnecting with built in error + return undefined; + } + + // reconnect after + return Math.max(options.attempt * 100, 3000); + } +}); + +// client.on('error', (err) => { +// throw err; +// }); + +client.ping((err) => { + if (err) { + console.error('Can\'t ping the redis server!'); + + throw err; + } + + debug('connection established'); +}); + +module.exports = client; diff --git a/routes/api/auth/index.js b/routes/api/auth/index.js new file mode 100644 index 000000000..57d126449 --- /dev/null +++ b/routes/api/auth/index.js @@ -0,0 +1,43 @@ +const express = require('express'); +const passport = require('../../../passport'); +const authorization = require('../../../middleware/authorization'); + +const router = express.Router(); + +router.get('/', authorization.needed(), (req, res) => { + res.json(req.user); +}); + +router.delete('/', (req, res) => { + req.logout(); + res.status(204).end(); +}); + +/** + * Local auth endpoint, will recieve a email and password + */ +router.post('/local', (req, res, next) => { + + // Perform the local authentication. + passport.authenticate('local', (err, user) => { + if (err) { + return next(err); + } + + if (!user) { + return next(authorization.ErrNotAuthorized); + } + + // Perform the login of the user! + req.logIn(user, (err) => { + if (err) { + return next(err); + } + + // We logged in the user! Let's send back the user data. + res.json({user}); + }); + })(req, res, next); +}); + +module.exports = router; diff --git a/routes/api/index.js b/routes/api/index.js index d161cf622..46570f588 100644 --- a/routes/api/index.js +++ b/routes/api/index.js @@ -3,6 +3,7 @@ const express = require('express'); const router = express.Router(); router.use('/asset', require('./asset')); +router.use('/auth', require('./auth')); router.use('/comments', require('./comments')); router.use('/settings', require('./settings')); router.use('/stream', require('./stream')); diff --git a/routes/index.js b/routes/index.js index cde5b07c7..6fbc54ca9 100644 --- a/routes/index.js +++ b/routes/index.js @@ -6,7 +6,7 @@ router.use('/admin', require('./admin')); router.use('/embed', require('./embed')); router.get('/', (req, res) => { - return res.render('home', {}); + res.render('home', {}); }); module.exports = router; diff --git a/tests/routes/api/auth/index.js b/tests/routes/api/auth/index.js new file mode 100644 index 000000000..dd49a1472 --- /dev/null +++ b/tests/routes/api/auth/index.js @@ -0,0 +1,39 @@ +require('../../../utils/mongoose'); + +const app = require('../../../../app'); +const chai = require('chai'); +const expect = chai.expect; + +chai.use(require('chai-http')); + +const User = require('../../../../models/user'); + +describe('POST /auth/local', () => { + + beforeEach(() => { + return User.createLocalUser('maria@gmail.com', 'password!', 'Maria'); + }); + + it('should send back the user on a successful login', () => { + return chai.request(app) + .post('/api/v1/auth/local') + .send({email: 'maria@gmail.com', password: 'password!'}) + .catch((res) => { + expect(res).to.have.status(200); + expect(res).to.be.json; + expect(res.body).to.have.property('user'); + expect(res.body.user).to.have.property('displayName', 'Maria'); + }); + }); + + it('should not send back the user on a unsuccessful login', () => { + return chai.request(app) + .post('/api/v1/auth/local') + .send({email: 'maria@gmail.com', password: 'password!3'}) + .catch((err) => { + expect(err).to.not.be.null; + expect(err.response).to.have.status(401); + expect(err.response.body).to.have.property('message', 'not authorized'); + }); + }); +}); diff --git a/views/admin.ejs~HEAD b/views/admin.ejs~HEAD deleted file mode 100644 index 038fb58d7..000000000 --- a/views/admin.ejs~HEAD +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - Talk - Coral Admin - - - - - -
- - - From 6c13e1c1c99fff0ef8744d3558df76776f0b927a Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 10 Nov 2016 17:22:14 -0700 Subject: [PATCH 02/86] Added fallback for secret during testing --- app.js | 4 ++++ tests/routes/api/comments/index.js | 13 +++---------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/app.js b/app.js index ed928622c..d945d6254 100644 --- a/app.js +++ b/app.js @@ -52,6 +52,10 @@ if (app.get('env') === 'production') { // Enable the secure cookie when we are in production mode. session_opts.cookie.secure = true; +} else if (app.get('env') === 'test') { + + // Add in the secret during tests. + session_opts.secret = 'keyboard cat'; } app.use(session(session_opts)); diff --git a/tests/routes/api/comments/index.js b/tests/routes/api/comments/index.js index e635e988c..900cdac37 100644 --- a/tests/routes/api/comments/index.js +++ b/tests/routes/api/comments/index.js @@ -168,15 +168,13 @@ describe('Get moderation queues rejected, pending, flags', () => { }); }); - it('should return all the flagged comments', function(done){ - chai.request(app) + it('should return all the flagged comments', () => { + return chai.request(app) .get('/api/v1/comments/action/flag') - .end(function(err, res){ + .then((res) => { expect(res).to.have.status(200); - expect(err).to.be.null; expect(res.body.length).to.equal(1); expect(res.body[0]).to.have.property('id', 'abc'); - done(); }); }); }); @@ -392,11 +390,6 @@ describe('Remove /:comment_id', () => { }); }); -process.on('unhandledRejection', (reason) => { - console.error('Reason: '); - console.error(reason); -}); - describe('Post /:comment_id/status', () => { const comments = [{ From f6184fddb09a1787fed259b046a66c919269a12f Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Fri, 11 Nov 2016 12:53:51 -0700 Subject: [PATCH 03/86] Added facebook login support + redis cache --- README.md | 9 ++-- app.js | 1 + cache.js | 97 ++++++++++++++++++++++++++++++++++++ passport.js | 2 +- routes/api/auth/index.js | 89 ++++++++++++++++++++++++++------- routes/api/comments/index.js | 2 +- views/auth-callback.ejs | 9 ++++ 7 files changed, 185 insertions(+), 24 deletions(-) create mode 100644 cache.js create mode 100644 views/auth-callback.ejs diff --git a/README.md b/README.md index ffc1f3077..0f217d89c 100644 --- a/README.md +++ b/README.md @@ -19,9 +19,12 @@ Runs Talk. The Talk application requires specific configuration options to be available inside the environment in order to run, those variables are listed here: -- `TALK_SESSION_SECRET` (*required*) - -- `TALK_FACEBOOK_APP_ID` (*required*) - -- `TALK_FACEBOOK_APP_SECRET` (*required*) - +- `TALK_SESSION_SECRET` (*required*) - a random string which will be used to + secure cookies +- `TALK_FACEBOOK_APP_ID` (*required*) - the Facebook app id for your Facebook + Login enabled app. +- `TALK_FACEBOOK_APP_SECRET` (*required*) - the Facebook app secret for your + Facebook Login enabled app. - `TALK_ROOT_URL` (*required*) - Root url of the installed application externally available in the format: `://` without the path. ### Running with Docker diff --git a/app.js b/app.js index d945d6254..f5b39a074 100644 --- a/app.js +++ b/app.js @@ -38,6 +38,7 @@ const session_opts = { rolling: true, saveUninitialized: false, resave: false, + name: 'talk.sid', cookie: { secure: false, maxAge: 18000000, // 30 minutes for expiry. diff --git a/cache.js b/cache.js new file mode 100644 index 000000000..efe689f9c --- /dev/null +++ b/cache.js @@ -0,0 +1,97 @@ +const redis = require('./redis'); + +const cache = module.exports = {}; + +/** + * This collects a key that may either be an array or a string and creates a + * unified key out of it. + * @param {Mixed} key Either an array of items composing a key or a string + * @return {String} A string that represents a key + */ +const keyfunc = (key) => { + if (Array.isArray(key)) { + return `cache[${key.join(':')}]`; + } + + return `cache[${key}]`; +}; + +/** + * This wraps a complicated function with a cache, in the event that the item is + * not inside the cache, it will perform the work to get it and then set it + * followed by returning the value. + * @param {Mixed} key Either an array of items or string represening this + * work + * @param {Integer} expiry Time in seconds for the cache entry to live for + * @param {Function} work A function that returns a promise that can be + * resolved as the value to cache. + * @return {Promise} Resolves to the value either retrieved from cache + */ +cache.wrap = (key, expiry, work) => { + return cache + .get(key) + .then((value) => { + if (value !== null) { + return value; + } + + return work() + .then((value) => { + return cache + .set(key, value, expiry) + .then(() => value); + }); + }); +}; + +/** + * This returns a promise that returns a promise that resolves with the value + * from the cache or null if it does not exist in the cache. + * @param {Mixed} key Either an array of items composing a key or a string + * @return {Promise} + */ +cache.get = (key) => new Promise((resolve, reject) => { + redis.get(keyfunc(key), (err, reply) => { + if (err) { + return reject(err); + } + + if (reply !== null) { + let value; + + try { + + // Parse the stored cache value from JSON. + value = JSON.parse(reply); + } catch (e) { + return reject(e); + } + + return resolve(value); + } + + resolve(null); + }); +}); + +/** + * This sets a value on the key with the expiry and then resolves once it is + * done. + * @param {Mixed} key Either an array of items composing a key or a string + * @param {Mixed} value Object to be serialized and set to the cache + * @param {Integer} expiry Time in seconds for the cache entry to live for + * @return {Promise} + */ +cache.set = (key, value, expiry) => new Promise((resolve, reject) => { + + // Serialize the value as JSON. + let reply = JSON.stringify(value); + + redis.set(keyfunc(key), reply, 'EX', expiry, (err) => { + if (err) { + return reject(err); + } + + return resolve(); + }); +}); diff --git a/passport.js b/passport.js index ae4776894..38a92075a 100644 --- a/passport.js +++ b/passport.js @@ -65,7 +65,7 @@ if (process.env.TALK_FACEBOOK_APP_ID && process.env.TALK_FACEBOOK_APP_SECRET && passport.use(new FacebookStrategy({ clientID: process.env.TALK_FACEBOOK_APP_ID, clientSecret: process.env.TALK_FACEBOOK_APP_SECRET, - callbackURL: `${process.env.TALK_ROOT_URL}/connect/facebook/callback` + callbackURL: `${process.env.TALK_ROOT_URL}/api/v1/auth/facebook/callback` }, (accessToken, refreshToken, profile, done) => { User .findOrCreateExternalUser(profile) diff --git a/routes/api/auth/index.js b/routes/api/auth/index.js index 57d126449..2c6f91676 100644 --- a/routes/api/auth/index.js +++ b/routes/api/auth/index.js @@ -4,40 +4,91 @@ const authorization = require('../../../middleware/authorization'); const router = express.Router(); +/** + * This returns the user if they are logged in. + */ router.get('/', authorization.needed(), (req, res) => { res.json(req.user); }); -router.delete('/', (req, res) => { - req.logout(); - res.status(204).end(); +/** + * This destroys the session of a user, if they have one. + */ +router.delete('/', authorization.needed(), (req, res) => { + req.session.destroy(() => { + res.status(204).end(); + }); }); +/** + * This sends back the user data as JSON. + */ +const HandleAuthCallback = (req, res, next) => (err, user) => { + if (err) { + return next(err); + } + + if (!user) { + return next(authorization.ErrNotAuthorized); + } + + // Perform the login of the user! + req.logIn(user, (err) => { + if (err) { + return next(err); + } + + // We logged in the user! Let's send back the user data. + res.json({user}); + }); +}; + +/** + * Returns the response to the login attempt via a popup callback with some JS. + */ +const HandleAuthPopupCallback = (req, res, next) => (err, user) => { + if (err) { + return res.render('auth-callback', {err: JSON.stringify(err), data: null}); + } + + if (!user) { + return res.render('auth-callback', {err: JSON.stringify(authorization.ErrNotAuthorized), data: null}); + } + + // Perform the login of the user! + req.logIn(user, (err) => { + if (err) { + return res.render('auth-callback', {err: JSON.stringify(err), data: null}); + } + + // We logged in the user! Let's send back the user data. + res.render('auth-callback', {err: null, data: JSON.stringify(user)}); + }); +}; + /** * Local auth endpoint, will recieve a email and password */ router.post('/local', (req, res, next) => { // Perform the local authentication. - passport.authenticate('local', (err, user) => { - if (err) { - return next(err); - } + passport.authenticate('local', HandleAuthCallback(req, res, next))(req, res, next); +}); - if (!user) { - return next(authorization.ErrNotAuthorized); - } +/** + * Facebook auth endpoint, this will redirect the user immediatly to facebook + * for authorization. + */ +router.get('/facebook', passport.authenticate('facebook')); - // Perform the login of the user! - req.logIn(user, (err) => { - if (err) { - return next(err); - } +/** + * Facebook callback endpoint, this will send the user a html page designed to + * send back the user credentials upon sucesfull login. + */ +router.get('/facebook/callback', (req, res, next) => { - // We logged in the user! Let's send back the user data. - res.json({user}); - }); - })(req, res, next); + // Perform the facebook login flow and pass the data back through the opener. + passport.authenticate('facebook', HandleAuthPopupCallback(req, res, next))(req, res, next); }); module.exports = router; diff --git a/routes/api/comments/index.js b/routes/api/comments/index.js index a2e77b417..bafc7dbcc 100644 --- a/routes/api/comments/index.js +++ b/routes/api/comments/index.js @@ -94,7 +94,7 @@ router.post('/:comment_id', (req, res, next) => { }); router.post('/:comment_id/status', (req, res, next) => { - + Comment .changeStatus(req.params.comment_id, req.body.status) .then(comment => res.status(200).send(comment)) diff --git a/views/auth-callback.ejs b/views/auth-callback.ejs new file mode 100644 index 000000000..d38d759ee --- /dev/null +++ b/views/auth-callback.ejs @@ -0,0 +1,9 @@ + + + + + + From 1adac5cde1186282975f93db26a3db75f4592c5f Mon Sep 17 00:00:00 2001 From: David Jay Date: Fri, 11 Nov 2016 16:25:47 -0500 Subject: [PATCH 04/86] Adding like button. --- .../coral-embed-stream/src/CommentStream.js | 55 +++++++++++++------ client/coral-embed-stream/style/default.css | 21 ++++++- client/coral-plugin-flags/FlagButton.js | 24 ++++---- client/coral-plugin-likes/LikeButton.js | 35 ++++++++++++ client/coral-plugin-likes/translations.json | 10 ++++ client/coral-plugin-replies/ReplyButton.js | 2 +- 6 files changed, 116 insertions(+), 31 deletions(-) create mode 100644 client/coral-plugin-likes/LikeButton.js create mode 100644 client/coral-plugin-likes/translations.json diff --git a/client/coral-embed-stream/src/CommentStream.js b/client/coral-embed-stream/src/CommentStream.js index d6edfddda..e3832a0dc 100644 --- a/client/coral-embed-stream/src/CommentStream.js +++ b/client/coral-embed-stream/src/CommentStream.js @@ -14,6 +14,7 @@ import AuthorName from '../../coral-plugin-author-name/AuthorName'; import {ReplyBox, ReplyButton} from '../../coral-plugin-replies'; import Pym from 'pym.js'; import FlagButton from '../../coral-plugin-flags/FlagButton'; +import LikeButton from '../../coral-plugin-likes/LikeButton'; const {addItem, updateItem, postItem, getStream, postAction, appendItemArray} = itemActions; const {addNotification, clearNotification} = notificationActions; @@ -119,7 +120,20 @@ class CommentStream extends Component { -
+
+ + +
+
-
-
- - -
+
+ + +
+
+ +
; }) } diff --git a/client/coral-embed-stream/style/default.css b/client/coral-embed-stream/style/default.css index e1c730bfc..f68e2a0e3 100644 --- a/client/coral-embed-stream/style/default.css +++ b/client/coral-embed-stream/style/default.css @@ -105,16 +105,33 @@ hr { /* Comment Action Styles */ -.commentActions, .replyActions { +.commentActionsRight, .replyActionsRight { display: flex; justify-content: flex-end; + width: 50%; +} +.commentActionsLeft, .replyActionsLeft { + display: flex; + justify-content: flex-start; + float: left; + width: 50%; } -.commentActions .material-icons, .replyActions .material-icons { +.commentActionsLeft .material-icons,.commentActionsRight .material-icons, +.replyActionsLeft .material-icons, .replyActionsRight .material-icons { font-size: 12px; + margin-left: 3px; vertical-align: middle; } +.likedButton { + color: rgb(0,134,227); +} + +.flaggedIcon { + color: #F00; +} + /* Comment count styles */ .coral-plugin-comment-count-text { margin-bottom: 15px; diff --git a/client/coral-plugin-flags/FlagButton.js b/client/coral-plugin-flags/FlagButton.js index 4f90dfb7c..7374def5f 100644 --- a/client/coral-plugin-flags/FlagButton.js +++ b/client/coral-plugin-flags/FlagButton.js @@ -7,24 +7,26 @@ const name = 'coral-plugin-flags'; const FlagButton = ({flag, id, postAction, addItem, updateItem, addNotification}) => { const flagged = flag && flag.current_user; const onFlagClick = () => { - postAction(id, 'flag', '123', 'comments') - .then((action) => { - addItem({...action, current_user:true}, 'actions'); - updateItem(action.item_id, action.action_type, action.id, 'comments'); - }); - addNotification('success', lang.t('flag-notif')); + if (!flagged) { + postAction(id, 'flag', '123', 'comments') + .then((action) => { + addItem({...action, current_user:true}, 'actions'); + updateItem(action.item_id, action.action_type, action.id, 'comments'); + }); + addNotification('success', lang.t('flag-notif')); + } }; return
; }; diff --git a/client/coral-plugin-likes/LikeButton.js b/client/coral-plugin-likes/LikeButton.js new file mode 100644 index 000000000..ec8528793 --- /dev/null +++ b/client/coral-plugin-likes/LikeButton.js @@ -0,0 +1,35 @@ +import React from 'react'; +import {I18n} from '../coral-framework'; +import translations from './translations.json'; + +const name = 'coral-plugin-flags'; + +const LikeButton = ({like, id, postAction, addItem, updateItem}) => { + const liked = like && like.current_user; + const onLikeClick = () => { + if (!liked) { + postAction(id, 'like', '123', 'comments') + .then((action) => { + addItem({id: action.id, current_user:true, count: like ? like.count + 1 : 1}, 'actions'); + updateItem(action.item_id, action.action_type, action.id, 'comments'); + }); + } + }; + + return
+ +
; +}; + +export default LikeButton; + +const lang = new I18n(translations); diff --git a/client/coral-plugin-likes/translations.json b/client/coral-plugin-likes/translations.json new file mode 100644 index 000000000..93d73d3a2 --- /dev/null +++ b/client/coral-plugin-likes/translations.json @@ -0,0 +1,10 @@ +{ + "en": { + "like": "Like", + "liked": "Liked" + }, + "es": { + "like": "Me Gusta", + "liked": "Me Gustó" + } +} diff --git a/client/coral-plugin-replies/ReplyButton.js b/client/coral-plugin-replies/ReplyButton.js index 44213c2f7..4fbfd5f60 100644 --- a/client/coral-plugin-replies/ReplyButton.js +++ b/client/coral-plugin-replies/ReplyButton.js @@ -7,9 +7,9 @@ const name = 'coral-plugin-replies'; const ReplyButton = (props) => ; export default ReplyButton; From 68433ec973d349d6500169eb593b0093b43d412c Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Fri, 11 Nov 2016 14:27:48 -0700 Subject: [PATCH 05/86] Added "display: popup" to the facebook dialog --- routes/api/auth/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes/api/auth/index.js b/routes/api/auth/index.js index 2c6f91676..37b41df6b 100644 --- a/routes/api/auth/index.js +++ b/routes/api/auth/index.js @@ -79,7 +79,7 @@ router.post('/local', (req, res, next) => { * Facebook auth endpoint, this will redirect the user immediatly to facebook * for authorization. */ -router.get('/facebook', passport.authenticate('facebook')); +router.get('/facebook', passport.authenticate('facebook', {display: 'popup'})); /** * Facebook callback endpoint, this will send the user a html page designed to From 92869e529abdd41dbba8b2b332938b2953543f78 Mon Sep 17 00:00:00 2001 From: David Jay Date: Fri, 11 Nov 2016 16:27:56 -0500 Subject: [PATCH 06/86] Fixing comment count bug. --- client/coral-plugin-comment-count/CommentCount.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/coral-plugin-comment-count/CommentCount.js b/client/coral-plugin-comment-count/CommentCount.js index 47f00ffb5..6786d5c74 100644 --- a/client/coral-plugin-comment-count/CommentCount.js +++ b/client/coral-plugin-comment-count/CommentCount.js @@ -5,12 +5,12 @@ const name = 'coral-plugin-comment-count'; const CommentCount = ({items, id}) => { let count = 0; - if (items[id]) { - count += items[id].comments.length; + if (items.assets[id]) { + count += items.assets[id].comments.length; } - const itemKeys = Object.keys(items); + const itemKeys = Object.keys(items.comments); for (let i = 0; i < itemKeys.length; i++) { - const item = items[itemKeys[i]]; + const item = items.comments[itemKeys[i]]; if (item.children) { count += item.children.length; } From 80e12d2e303b14e7d4ed1e51e6688912fec69276 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Fri, 11 Nov 2016 14:59:27 -0700 Subject: [PATCH 07/86] Added photo support --- models/user.js | 2 ++ passport.js | 3 ++- routes/api/auth/index.js | 4 ++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/models/user.js b/models/user.js index 6800b59ef..2cfb03fac 100644 --- a/models/user.js +++ b/models/user.js @@ -12,6 +12,7 @@ const UserSchema = new mongoose.Schema({ required: true }, displayName: String, + photo: String, disabled: Boolean, password: String, profiles: [{ @@ -139,6 +140,7 @@ UserSchema.statics.findOrCreateExternalUser = function(profile) { user = new User({ displayName: profile.displayName, roles: [], + photo: Array.isArray(profile.photos) && profile.photos.length > 0 ? profile.photos[0].value : null, profiles: [ { id: profile.id, diff --git a/passport.js b/passport.js index 38a92075a..2e084eafd 100644 --- a/passport.js +++ b/passport.js @@ -65,7 +65,8 @@ if (process.env.TALK_FACEBOOK_APP_ID && process.env.TALK_FACEBOOK_APP_SECRET && passport.use(new FacebookStrategy({ clientID: process.env.TALK_FACEBOOK_APP_ID, clientSecret: process.env.TALK_FACEBOOK_APP_SECRET, - callbackURL: `${process.env.TALK_ROOT_URL}/api/v1/auth/facebook/callback` + callbackURL: `${process.env.TALK_ROOT_URL}/api/v1/auth/facebook/callback`, + profileFields: ['id', 'displayName', 'picture.type(large)'] }, (accessToken, refreshToken, profile, done) => { User .findOrCreateExternalUser(profile) diff --git a/routes/api/auth/index.js b/routes/api/auth/index.js index 37b41df6b..87ffb62c8 100644 --- a/routes/api/auth/index.js +++ b/routes/api/auth/index.js @@ -14,7 +14,7 @@ router.get('/', authorization.needed(), (req, res) => { /** * This destroys the session of a user, if they have one. */ -router.delete('/', authorization.needed(), (req, res) => { +router.delete('/', (req, res) => { req.session.destroy(() => { res.status(204).end(); }); @@ -79,7 +79,7 @@ router.post('/local', (req, res, next) => { * Facebook auth endpoint, this will redirect the user immediatly to facebook * for authorization. */ -router.get('/facebook', passport.authenticate('facebook', {display: 'popup'})); +router.get('/facebook', passport.authenticate('facebook', {display: 'popup', authType: 'rerequest', scope: ['public_profile']})); /** * Facebook callback endpoint, this will send the user a html page designed to From 4bea5e5914f3a978bf04594a6e294b2890bc1fab Mon Sep 17 00:00:00 2001 From: David Jay Date: Fri, 11 Nov 2016 17:06:29 -0500 Subject: [PATCH 08/86] Adding route to delete actions. --- models/comment.js | 21 ++++++++++++++++++--- routes/api/comments/index.js | 19 +++++++++++++++---- tests/models/comment.js | 11 +++++++++++ 3 files changed, 44 insertions(+), 7 deletions(-) diff --git a/models/comment.js b/models/comment.js index 8eab6aff4..492c06e9c 100644 --- a/models/comment.js +++ b/models/comment.js @@ -104,14 +104,14 @@ CommentSchema.statics.findByStatusByActionType = function(status, action_type) { return Action .findCommentsIdByActionType(action_type, 'comment') .then((actions) => { - + return Comment.find({ - 'status': status, + 'status': status, 'id': { '$in': actions.map(a => { return a.item_id; }) - } + } }); }); @@ -188,6 +188,21 @@ CommentSchema.statics.removeById = function(id) { return Comment.remove({'id': id}); }; +/** + * Remove an action from the comment. + * @param {String} id identifier of the comment (uuid) + * @param {String} action_type the type of the action to be removed + * @param {String} user_id the id of the user performing the action +*/ +CommentSchema.statics.removeAction = function(id, user_id, action_type) { + return Action.remove({ + action_type: action_type, + item_type: 'comment', + item_id: id, + user_id: user_id + }); +}; + const Comment = mongoose.model('Comment', CommentSchema); module.exports = Comment; diff --git a/routes/api/comments/index.js b/routes/api/comments/index.js index 5126bf93c..525dc87e6 100644 --- a/routes/api/comments/index.js +++ b/routes/api/comments/index.js @@ -54,7 +54,7 @@ router.get('/status/rejected', (req, res, next) => { // Pre-moderation: New comments are shown in the moderator queues immediately. // Post-moderation: New comments do not appear in moderation queues unless they are flagged by other users. router.get('/status/pending', (req, res, next) => { - + Setting .getModerationSetting() .then(({moderation}) => { @@ -76,9 +76,9 @@ router.get('/status/pending', (req, res, next) => { //============================================================================== router.post('/', (req, res, next) => { - + const {body, author_id, asset_id, parent_id, status, username} = req.body; - + Comment .new(body, author_id, asset_id, parent_id, status, username) .then((comment) => { @@ -109,7 +109,7 @@ router.post('/:comment_id', (req, res, next) => { }); router.post('/:comment_id/status', (req, res, next) => { - + Comment .changeStatus(req.params.comment_id, req.body.status) .then(comment => res.status(200).send(comment)) @@ -143,4 +143,15 @@ router.delete('/:comment_id', (req, res, next) => { }); }); +router.delete('/:comment_id/actions', (req, res, next) => { + Comment + .removeAction(req.params.comment_id, req.body.user_id, req.body.action_type) + .then(() => { + res.status(201).send('OK. Removed'); + }) + .catch(error => { + next(error); + }); +}); + module.exports = router; diff --git a/tests/models/comment.js b/tests/models/comment.js index 94e45625a..f8dd7f9f5 100644 --- a/tests/models/comment.js +++ b/tests/models/comment.js @@ -134,4 +134,15 @@ describe('Comment: models', () => { // }); // }); }); + + describe('#removeAction', () => { + it('should remove an action', () => { + return Comment.removeAction('3', '123', 'flag').then(() => { + return Action.findByItemIdArray(['123']); + }) + .then((actions) => { + expect(actions.length).to.equal(0); + }); + }); + }); }); From 103acd4bce2aab37d962a08ff4d8ab0e63ff8f4f Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Fri, 11 Nov 2016 20:18:22 -0300 Subject: [PATCH 09/86] Continue with Facebook Funtionalit --- client/coral-admin/src/components/Comment.js | 2 +- .../src/components/ModerationKeysModal.js | 2 +- .../src/containers/Community/Community.js | 2 +- .../coral-admin/src/containers/Configure.js | 2 +- .../src/containers/ModerationQueue.js | 2 +- .../coral-embed-stream/src/CommentStream.js | 2 + client/coral-framework/actions/auth.js | 61 +++++++++++++++++++ .../{store => }/actions/config.js | 2 - .../{store => }/actions/items.js | 0 .../{store => }/actions/notification.js | 2 - client/coral-framework/constants/auth.js | 9 +++ client/coral-framework/helpers/index.js | 27 ++++++++ client/coral-framework/index.js | 14 ++--- .../{ => modules}/i18n/i18n.js | 2 +- .../notification/Notification.js | 0 client/coral-framework/reducers/auth.js | 22 +++++++ .../{store => }/reducers/config.js | 0 .../{store => }/reducers/index.js | 0 .../{store => }/reducers/items.js | 0 .../{store => }/reducers/notification.js | 0 client/coral-framework/{store => }/store.js | 1 - client/coral-framework/store/actions/auth.js | 17 ------ client/coral-framework/store/reducers/auth.js | 17 ------ .../PermalinkButton.js | 2 +- client/coral-sign-in/components/Button.js | 13 ++++ client/coral-sign-in/components/SignIn.js | 15 +++++ client/coral-sign-in/components/styles.css | 43 +++++++++++++ .../containers/SignInContainer.js | 37 +++++++++++ .../coral-framework/store/authReducer.js | 31 ---------- .../coral-framework/store/itemActions.spec.js | 2 +- .../coral-framework/store/itemReducer.spec.js | 2 +- .../store/notificationReducer.spec.js | 4 +- 32 files changed, 247 insertions(+), 88 deletions(-) create mode 100644 client/coral-framework/actions/auth.js rename client/coral-framework/{store => }/actions/config.js (98%) rename client/coral-framework/{store => }/actions/items.js (100%) rename client/coral-framework/{store => }/actions/notification.js (92%) create mode 100644 client/coral-framework/constants/auth.js create mode 100644 client/coral-framework/helpers/index.js rename client/coral-framework/{ => modules}/i18n/i18n.js (96%) rename client/coral-framework/{ => modules}/notification/Notification.js (100%) create mode 100644 client/coral-framework/reducers/auth.js rename client/coral-framework/{store => }/reducers/config.js (100%) rename client/coral-framework/{store => }/reducers/index.js (100%) rename client/coral-framework/{store => }/reducers/items.js (100%) rename client/coral-framework/{store => }/reducers/notification.js (100%) rename client/coral-framework/{store => }/store.js (99%) delete mode 100644 client/coral-framework/store/actions/auth.js delete mode 100644 client/coral-framework/store/reducers/auth.js create mode 100644 client/coral-sign-in/components/Button.js create mode 100644 client/coral-sign-in/components/SignIn.js create mode 100644 client/coral-sign-in/components/styles.css create mode 100644 client/coral-sign-in/containers/SignInContainer.js delete mode 100644 tests/client/coral-framework/store/authReducer.js diff --git a/client/coral-admin/src/components/Comment.js b/client/coral-admin/src/components/Comment.js index 2b0b7561b..e5a753f80 100644 --- a/client/coral-admin/src/components/Comment.js +++ b/client/coral-admin/src/components/Comment.js @@ -3,7 +3,7 @@ import React from 'react'; import {Button, Icon} from 'react-mdl'; import timeago from 'timeago.js'; import styles from './CommentList.css'; -import I18n from 'coral-framework/i18n/i18n'; +import I18n from 'coral-framework/modules/i18n/i18n'; import translations from '../translations'; // Render a single comment for the list diff --git a/client/coral-admin/src/components/ModerationKeysModal.js b/client/coral-admin/src/components/ModerationKeysModal.js index 5cf9fe17e..b1282e5d6 100644 --- a/client/coral-admin/src/components/ModerationKeysModal.js +++ b/client/coral-admin/src/components/ModerationKeysModal.js @@ -1,4 +1,4 @@ -import I18n from 'coral-framework/i18n/i18n'; +import I18n from 'coral-framework/modules/i18n/i18n'; import translations from '../translations'; import React from 'react'; import Modal from 'components/Modal'; diff --git a/client/coral-admin/src/containers/Community/Community.js b/client/coral-admin/src/containers/Community/Community.js index 8e0b955a4..5c1bb2e19 100644 --- a/client/coral-admin/src/containers/Community/Community.js +++ b/client/coral-admin/src/containers/Community/Community.js @@ -1,5 +1,5 @@ import React from 'react'; -import I18n from 'coral-framework/i18n/i18n'; +import I18n from 'coral-framework/modules/i18n/i18n'; import translations from '../../translations'; import {Grid, Cell} from 'react-mdl'; diff --git a/client/coral-admin/src/containers/Configure.js b/client/coral-admin/src/containers/Configure.js index 229c4e948..952e2acd1 100644 --- a/client/coral-admin/src/containers/Configure.js +++ b/client/coral-admin/src/containers/Configure.js @@ -13,7 +13,7 @@ import { Icon } from 'react-mdl'; import styles from './Configure.css'; -import I18n from 'coral-framework/i18n/i18n'; +import I18n from 'coral-framework/modules/i18n/i18n'; import translations from '../translations'; class Configure extends React.Component { diff --git a/client/coral-admin/src/containers/ModerationQueue.js b/client/coral-admin/src/containers/ModerationQueue.js index 9178bda57..a92080c39 100644 --- a/client/coral-admin/src/containers/ModerationQueue.js +++ b/client/coral-admin/src/containers/ModerationQueue.js @@ -5,7 +5,7 @@ import CommentList from 'components/CommentList'; import {updateStatus} from 'actions/comments'; import styles from './ModerationQueue.css'; import key from 'keymaster'; -import I18n from 'coral-framework/i18n/i18n'; +import I18n from 'coral-framework/modules/i18n/i18n'; import translations from '../translations'; /* diff --git a/client/coral-embed-stream/src/CommentStream.js b/client/coral-embed-stream/src/CommentStream.js index bfccf7cfc..31457787a 100644 --- a/client/coral-embed-stream/src/CommentStream.js +++ b/client/coral-embed-stream/src/CommentStream.js @@ -15,6 +15,7 @@ import {ReplyBox, ReplyButton} from '../../coral-plugin-replies'; import Pym from 'pym.js'; import FlagButton from '../../coral-plugin-flags/FlagButton'; import PermalinkButton from '../../coral-plugin-permalinks/PermalinkButton'; +import SignInContainer from '../../coral-sign-in/containers/SignInContainer'; const {addItem, updateItem, postItem, getStream, postAction, appendItemArray} = itemActions; const {addNotification, clearNotification} = notificationActions; @@ -111,6 +112,7 @@ class CommentStream extends Component { id={rootItemId} premod={this.props.config.moderation} reply={false}/> + { rootItem.comments.map((commentId) => { diff --git a/client/coral-framework/actions/auth.js b/client/coral-framework/actions/auth.js new file mode 100644 index 000000000..9b1053161 --- /dev/null +++ b/client/coral-framework/actions/auth.js @@ -0,0 +1,61 @@ +import * as actions from '../constants/auth'; +import {base, handleResp} from '../helpers'; + +export const loginFacebook = () => dispatch => { + dispatch(loginFacebookRequest()); + window.open( + `${base}/auth/facebook`, + 'Continue with Facebook', + 'menubar=0,resizable=0,width=500,height=500,top=200,left=500' + ); +}; + +export const loginFacebookCallback = (err, data) => { + let user; + + if (err) { + console.error(err); + return; + } + + try { + user = JSON.parse(data); + } catch (err) { + console.error('Can\'t parse the user json', err); + return; + } + + console.log('User was loaded!', user); +}; + +export const logout = () => dispatch => { + dispatch(logoutRequest()); + fetch(`${base}/api/v1/auth`, { + method: 'DELETE', + credentials: 'same-origin' + }) + .then(handleResp) + .then(() => dispatch(dispatch(logoutSuccess()))) + .catch(error => dispatch(logoutFailure(error))); +}; + +const logoutRequest = () => ({ + type: actions.USER_LOGOUT_SUCCESS +}); + +const logoutSuccess = () => ({ + type: actions.USER_LOGOUT_SUCCESS +}); + +const logoutFailure = (error) => ({ + type: actions.USER_LOGOUT_SUCCESS, + error +}); + +export const loginFacebookRequest = () => ({ + type: actions.USER_FACEBOOK_LOGIN_REQUEST +}); + +export const loginRequest = () => ({ + type: actions.USER_SIGNIN_REQUEST +}); diff --git a/client/coral-framework/store/actions/config.js b/client/coral-framework/actions/config.js similarity index 98% rename from client/coral-framework/store/actions/config.js rename to client/coral-framework/actions/config.js index f131b8c82..d8fd886be 100644 --- a/client/coral-framework/store/actions/config.js +++ b/client/coral-framework/actions/config.js @@ -1,5 +1,3 @@ -/* @flow */ - import {fromJS} from 'immutable'; /** diff --git a/client/coral-framework/store/actions/items.js b/client/coral-framework/actions/items.js similarity index 100% rename from client/coral-framework/store/actions/items.js rename to client/coral-framework/actions/items.js diff --git a/client/coral-framework/store/actions/notification.js b/client/coral-framework/actions/notification.js similarity index 92% rename from client/coral-framework/store/actions/notification.js rename to client/coral-framework/actions/notification.js index c34adc414..4edc17601 100644 --- a/client/coral-framework/store/actions/notification.js +++ b/client/coral-framework/actions/notification.js @@ -1,5 +1,3 @@ -/* Notification Actions */ - export const ADD_NOTIFICATION = 'ADD_NOTIFICATION'; export const CLEAR_NOTIFICATION = 'CLEAR_NOTIFICATION'; diff --git a/client/coral-framework/constants/auth.js b/client/coral-framework/constants/auth.js new file mode 100644 index 000000000..7f93a51fe --- /dev/null +++ b/client/coral-framework/constants/auth.js @@ -0,0 +1,9 @@ +export const USER_LOGIN_REQUEST = 'USER_LOGIN_REQUEST'; +export const USER_LOGIN_SUCCESS = 'USER_LOGIN_SUCCESS'; +export const USER_LOGIN_FAILURE = 'USER_LOGIN_FAILURE'; +export const USER_FACEBOOK_LOGIN_REQUEST = 'USER_FACEBOOK_LOGIN_REQUEST'; +export const USER_FACEBOOK_SUCCESS = 'USER_FACEBOOK_SUCCESS'; +export const USER_FACEBOOK_FAILURE = 'USER_FACEBOOK_FAILURE'; +export const USER_LOGOUT_REQUEST = 'USER_LOGOUT_REQUEST'; +export const USER_LOGOUT_SUCCESS = 'USER_LOGOUT_SUCCESS'; +export const USER_LOGOUT_FAILURE = 'USER_LOGOUT_FAILURE'; diff --git a/client/coral-framework/helpers/index.js b/client/coral-framework/helpers/index.js new file mode 100644 index 000000000..439ba5a8a --- /dev/null +++ b/client/coral-framework/helpers/index.js @@ -0,0 +1,27 @@ +export const base = '/api/v1'; + +export const getInit = (method, body) => { + const headers = new Headers({ + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }); + + const init = {method, headers}; + if (method.toLowerCase() !== 'get') { + init.body = JSON.stringify(body); + } + + return init; +}; + +export const handleResp = res => { + if (res.status === 401) { + throw new Error('Not Authorized to make this request'); + } else if (res.status > 399) { + throw new Error('Error! Status ', res.status); + } else if (res.status === 204) { + return res.text(); + } else { + return res.json(); + } +}; diff --git a/client/coral-framework/index.js b/client/coral-framework/index.js index 118935c50..4c6ae741f 100644 --- a/client/coral-framework/index.js +++ b/client/coral-framework/index.js @@ -1,10 +1,10 @@ -import Notification from './notification/Notification'; -import store from './store/store'; -import {fetchConfig} from './store/actions/config'; -import * as itemActions from './store/actions/items'; -import I18n from './i18n/i18n'; -import * as notificationActions from './store/actions/notification'; -import * as authActions from './store/actions/auth'; +import Notification from './modules/notification/Notification'; +import store from './store'; +import {fetchConfig} from './actions/config'; +import * as itemActions from './actions/items'; +import I18n from './modules/i18n/i18n'; +import * as notificationActions from './actions/notification'; +import * as authActions from './actions/auth'; export { Notification, diff --git a/client/coral-framework/i18n/i18n.js b/client/coral-framework/modules/i18n/i18n.js similarity index 96% rename from client/coral-framework/i18n/i18n.js rename to client/coral-framework/modules/i18n/i18n.js index 43144f3a6..afe8606d1 100644 --- a/client/coral-framework/i18n/i18n.js +++ b/client/coral-framework/modules/i18n/i18n.js @@ -1,5 +1,5 @@ import timeago from 'timeago.js'; -import esTA from 'timeago.js/locales/es'; +import esTA from '../../../../node_modules/timeago.js/locales/es'; /** * Default locales, this should be overriden by config file diff --git a/client/coral-framework/notification/Notification.js b/client/coral-framework/modules/notification/Notification.js similarity index 100% rename from client/coral-framework/notification/Notification.js rename to client/coral-framework/modules/notification/Notification.js diff --git a/client/coral-framework/reducers/auth.js b/client/coral-framework/reducers/auth.js new file mode 100644 index 000000000..c585ea237 --- /dev/null +++ b/client/coral-framework/reducers/auth.js @@ -0,0 +1,22 @@ +import {Map} from 'immutable'; +// +//import { +// FETCH_COMMENTERS_REQUEST, +// FETCH_COMMENTERS_FAILURE, +// FETCH_COMMENTERS_SUCCESS, +// SORT_UPDATE +//} from '../constants/community'; +// +const initialState = Map({ + auth: Map(), + loggedIn: false, + error: '', + user: {} +}); + +export default function community (state = initialState, action) { + switch (action.type) { + default : + return state; + } +} diff --git a/client/coral-framework/store/reducers/config.js b/client/coral-framework/reducers/config.js similarity index 100% rename from client/coral-framework/store/reducers/config.js rename to client/coral-framework/reducers/config.js diff --git a/client/coral-framework/store/reducers/index.js b/client/coral-framework/reducers/index.js similarity index 100% rename from client/coral-framework/store/reducers/index.js rename to client/coral-framework/reducers/index.js diff --git a/client/coral-framework/store/reducers/items.js b/client/coral-framework/reducers/items.js similarity index 100% rename from client/coral-framework/store/reducers/items.js rename to client/coral-framework/reducers/items.js diff --git a/client/coral-framework/store/reducers/notification.js b/client/coral-framework/reducers/notification.js similarity index 100% rename from client/coral-framework/store/reducers/notification.js rename to client/coral-framework/reducers/notification.js diff --git a/client/coral-framework/store/store.js b/client/coral-framework/store.js similarity index 99% rename from client/coral-framework/store/store.js rename to client/coral-framework/store.js index 33cc9efb5..8c3f9edc1 100644 --- a/client/coral-framework/store/store.js +++ b/client/coral-framework/store.js @@ -1,4 +1,3 @@ - import {createStore, applyMiddleware} from 'redux'; import thunk from 'redux-thunk'; import mainReducer from './reducers'; diff --git a/client/coral-framework/store/actions/auth.js b/client/coral-framework/store/actions/auth.js deleted file mode 100644 index af1e3cb34..000000000 --- a/client/coral-framework/store/actions/auth.js +++ /dev/null @@ -1,17 +0,0 @@ -/* Auth Actions */ - -export const SET_LOGGED_IN_USER = 'SET_LOGGED_IN_USER'; -export const LOG_OUT_USER = 'LOG_OUT_USER'; - -export const setLoggedInUser = (user_id) => { - return { - type: SET_LOGGED_IN_USER, - user_id - }; -}; - -export const LogOutUser = () => { - return { - type: LOG_OUT_USER - }; -}; diff --git a/client/coral-framework/store/reducers/auth.js b/client/coral-framework/store/reducers/auth.js deleted file mode 100644 index b79444cc9..000000000 --- a/client/coral-framework/store/reducers/auth.js +++ /dev/null @@ -1,17 +0,0 @@ -/* Auth */ - -import * as actions from '../actions/auth'; -import {fromJS} from 'immutable'; - -const initialState = fromJS({}); - -export default (state = initialState, action) => { - switch (action.type) { - case actions.SET_LOGGED_IN_USER: - return state.set('user', action.user_id); - case actions.LOG_OUT_USER: - return initialState; - default: - return state; - } -}; diff --git a/client/coral-plugin-permalinks/PermalinkButton.js b/client/coral-plugin-permalinks/PermalinkButton.js index 978a6dd03..2372e2035 100644 --- a/client/coral-plugin-permalinks/PermalinkButton.js +++ b/client/coral-plugin-permalinks/PermalinkButton.js @@ -1,5 +1,5 @@ import React, {PropTypes} from 'react'; -import I18n from 'coral-framework/i18n/i18n'; +import I18n from 'coral-framework/modules/i18n/i18n'; import translations from './translations'; import onClickOutside from 'react-onclickoutside'; const name = 'coral-plugin-permalinks'; diff --git a/client/coral-sign-in/components/Button.js b/client/coral-sign-in/components/Button.js new file mode 100644 index 000000000..fd8cc85c7 --- /dev/null +++ b/client/coral-sign-in/components/Button.js @@ -0,0 +1,13 @@ +import React from 'react'; +import styles from './styles.css'; + +const Button = ({type = 'local', children, ...props}) => ( + +); + +export default Button; diff --git a/client/coral-sign-in/components/SignIn.js b/client/coral-sign-in/components/SignIn.js new file mode 100644 index 000000000..61f1e387e --- /dev/null +++ b/client/coral-sign-in/components/SignIn.js @@ -0,0 +1,15 @@ +import React from 'react'; +import Button from './Button'; + +const SignIn = ({openFacebookWindow, logout}) => ( +
+ + + Logout + +
+); + +export default SignIn; diff --git a/client/coral-sign-in/components/styles.css b/client/coral-sign-in/components/styles.css new file mode 100644 index 000000000..6dbbd5b2f --- /dev/null +++ b/client/coral-sign-in/components/styles.css @@ -0,0 +1,43 @@ +.button { + background: 0 0; + border: none; + border-radius: 2px; + color: #000; + display: block; + position: relative; + height: 36px; + min-width: 64px; + padding: 0 8px; + display: inline-block; + font-family: 'Roboto','Helvetica','Arial',sans-serif; + font-size: 14px; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0; + overflow: hidden; + will-change: box-shadow,transform; + -webkit-transition: box-shadow .2s cubic-bezier(.4,0,1,1),background-color .2s cubic-bezier(.4,0,.2,1),color .2s cubic-bezier(.4,0,.2,1); + transition: box-shadow .2s cubic-bezier(.4,0,1,1),background-color .2s cubic-bezier(.4,0,.2,1),color .2s cubic-bezier(.4,0,.2,1); + outline: none; + cursor: pointer; + text-decoration: none; + text-align: center; + line-height: 36px; + vertical-align: middle; +} + +.local { + background: #E0E0E0; + color: #212121; +} + +.facebook { + background-color: #4267b2; + border-color: #4267b2; + color: rgb(255, 255, 255); +} + +.facebook:hover { + background-color: #365899; + border-color: #365899; +} \ No newline at end of file diff --git a/client/coral-sign-in/containers/SignInContainer.js b/client/coral-sign-in/containers/SignInContainer.js new file mode 100644 index 000000000..72b46536e --- /dev/null +++ b/client/coral-sign-in/containers/SignInContainer.js @@ -0,0 +1,37 @@ +import React, {Component} from 'react'; +import {connect} from 'react-redux'; + +import SignIn from '../components/SignIn'; + +import { + loginFacebookCallback, + loginFacebook, + logout +} from '../../coral-framework/actions/auth'; + +class SignInContainer extends Component { + constructor(props) { + super(props); + + this.openFacebookWindow = this.openFacebookWindow.bind(this); + this.logout = this.logout.bind(this); + } + + componentDidMount() { + window.authCallback = loginFacebookCallback; + } + + openFacebookWindow() { + this.props.dispatch(loginFacebook()); + } + + logout() { + this.props.dispatch(logout()); + } + + render() { + return ; + } +} + +export default connect(({auth}) => ({auth}))(SignInContainer); diff --git a/tests/client/coral-framework/store/authReducer.js b/tests/client/coral-framework/store/authReducer.js deleted file mode 100644 index aaebe1897..000000000 --- a/tests/client/coral-framework/store/authReducer.js +++ /dev/null @@ -1,31 +0,0 @@ -import {Map} from 'immutable'; -import {expect} from 'chai'; -import authReducer from '../../../../client/coral-framework/store/reducers/auth'; -import * as actions from '../../../../client/coral-framework/store/actions/auth'; - -describe ('authReducer', () => { - describe('SET_LOGGED_IN_USER', () => { - it('should set a logged in user', () => { - const action = { - type: actions.SET_LOGGED_IN_USER, - user_id: '123' - }; - const store = new Map({}); - const result = authReducer(store, action); - expect(result.get('user')).to.equal(action.user_id); - }); - }); - - describe('LOG_OUT_USER', () => { - it('should clear the user store', () => { - const action = { - type: actions.LOG_OUT_USER - }; - const store = new Map({ - user: '123' - }); - const result = authReducer(store, action); - expect(result.get('user')).to.equal(undefined); - }); - }); -}); diff --git a/tests/client/coral-framework/store/itemActions.spec.js b/tests/client/coral-framework/store/itemActions.spec.js index d45212794..e50b82344 100644 --- a/tests/client/coral-framework/store/itemActions.spec.js +++ b/tests/client/coral-framework/store/itemActions.spec.js @@ -2,7 +2,7 @@ import 'react'; import 'redux'; import {expect} from 'chai'; import fetchMock from 'fetch-mock'; -import * as actions from '../../../../client/coral-framework/store/actions/items'; +import * as actions from '../../../../client/coral-framework/actions/items'; import {Map} from 'immutable'; import configureStore from 'redux-mock-store'; diff --git a/tests/client/coral-framework/store/itemReducer.spec.js b/tests/client/coral-framework/store/itemReducer.spec.js index 9aea845f3..16377327b 100644 --- a/tests/client/coral-framework/store/itemReducer.spec.js +++ b/tests/client/coral-framework/store/itemReducer.spec.js @@ -1,6 +1,6 @@ import {Map, fromJS} from 'immutable'; import {expect} from 'chai'; -import itemsReducer from '../../../../client/coral-framework/store/reducers/items'; +import itemsReducer from '../../../../client/coral-framework/reducers/items'; describe ('itemsReducer', () => { describe('ADD_ITEM', () => { diff --git a/tests/client/coral-framework/store/notificationReducer.spec.js b/tests/client/coral-framework/store/notificationReducer.spec.js index 60b944a33..960034119 100644 --- a/tests/client/coral-framework/store/notificationReducer.spec.js +++ b/tests/client/coral-framework/store/notificationReducer.spec.js @@ -1,7 +1,7 @@ import {Map} from 'immutable'; import {expect} from 'chai'; -import notificationReducer from '../../../../client/coral-framework/store/reducers/notification'; -import * as actions from '../../../../client/coral-framework/store/actions/notification'; +import notificationReducer from '../../../../client/coral-framework/reducers/notification'; +import * as actions from '../../../../client/coral-framework/actions/notification'; describe ('notificationsReducer', () => { describe('ADD_NOTIFICATION', () => { From b629cf359f69ca3ac742734f296ed174ad628a80 Mon Sep 17 00:00:00 2001 From: David Jay Date: Fri, 11 Nov 2016 19:07:15 -0500 Subject: [PATCH 10/86] Removing likes and flags on second click. --- .../coral-embed-stream/src/CommentStream.js | 9 +++- client/coral-framework/store/actions/items.js | 44 +++++++++++++++++-- client/coral-plugin-flags/FlagButton.js | 14 ++++-- client/coral-plugin-flags/translations.json | 6 ++- client/coral-plugin-likes/LikeButton.js | 10 ++++- .../coral-framework/store/itemActions.spec.js | 18 ++++++++ 6 files changed, 88 insertions(+), 13 deletions(-) diff --git a/client/coral-embed-stream/src/CommentStream.js b/client/coral-embed-stream/src/CommentStream.js index e3832a0dc..9270b7a01 100644 --- a/client/coral-embed-stream/src/CommentStream.js +++ b/client/coral-embed-stream/src/CommentStream.js @@ -16,7 +16,7 @@ import Pym from 'pym.js'; import FlagButton from '../../coral-plugin-flags/FlagButton'; import LikeButton from '../../coral-plugin-likes/LikeButton'; -const {addItem, updateItem, postItem, getStream, postAction, appendItemArray} = itemActions; +const {addItem, updateItem, postItem, getStream, postAction, deleteAction, appendItemArray} = itemActions; const {addNotification, clearNotification} = notificationActions; const {setLoggedInUser} = authActions; @@ -55,6 +55,9 @@ const mapDispatchToProps = (dispatch) => { postAction: (item, action, user, itemType) => { return dispatch(postAction(item, action, user, itemType)); }, + deleteAction: (item, action, user, itemType) => { + return dispatch(deleteAction(item, action, user, itemType)); + }, appendItemArray: (item, property, value, addToFront, itemType) => { return dispatch(appendItemArray(item, property, value, addToFront, itemType)); } @@ -129,6 +132,7 @@ class CommentStream extends Component { id={commentId} like={this.props.items.actions[comment.like]} postAction={this.props.postAction} + deleteAction={this.props.deleteAction} addItem={this.props.addItem} updateItem={this.props.updateItem} currentUser={this.props.auth.user}/> @@ -139,6 +143,7 @@ class CommentStream extends Component { id={commentId} flag={this.props.items.actions[comment.flag]} postAction={this.props.postAction} + deleteAction={this.props.deleteAction} addItem={this.props.addItem} updateItem={this.props.updateItem} currentUser={this.props.auth.user}/> @@ -170,6 +175,7 @@ class CommentStream extends Component { id={replyId} like={this.props.items.actions[reply.like]} postAction={this.props.postAction} + deleteAction={this.props.deleteAction} addItem={this.props.addItem} updateItem={this.props.updateItem} currentUser={this.props.auth.user}/> @@ -180,6 +186,7 @@ class CommentStream extends Component { id={replyId} flag={this.props.items.actions[reply.flag]} postAction={this.props.postAction} + deleteAction={this.props.deleteAction} addItem={this.props.addItem} updateItem={this.props.updateItem} currentUser={this.props.auth.user}/> diff --git a/client/coral-framework/store/actions/items.js b/client/coral-framework/store/actions/items.js index 07f8e54af..648a1d95e 100644 --- a/client/coral-framework/store/actions/items.js +++ b/client/coral-framework/store/actions/items.js @@ -241,9 +241,45 @@ export function postAction (item_id, action_type, user_id, item_type) { return response.ok ? response.json() : Promise.reject(`${response.status} ${response.statusText}`); } - ) - .then((json)=>{ - return json; - }); + ); + }; +} + +/* +* DeleteAction +* Deletes an action to an item +* +* @params +* id - the id of the item on which the action is taking place +* action - the name of the action +* user - the user performing the action +* host - the coral host +* +* @returns +* A promise resolving to null or an error +* +*/ + +export function deleteAction (item_id, action_type, user_id, item_type) { + return () => { + const action = { + action_type, + user_id + }; + const options = { + method: 'DELETE', + headers: { + 'Content-Type':'application/json' + }, + body: JSON.stringify(action) + }; + + return fetch(`/api/v1/${item_type}/${item_id}/actions`, options) + .then( + response => { + return response.ok ? response.text() + : Promise.reject(`${response.status} ${response.statusText}`); + } + ); }; } diff --git a/client/coral-plugin-flags/FlagButton.js b/client/coral-plugin-flags/FlagButton.js index 7374def5f..a4869c486 100644 --- a/client/coral-plugin-flags/FlagButton.js +++ b/client/coral-plugin-flags/FlagButton.js @@ -4,7 +4,7 @@ import translations from './translations.json'; const name = 'coral-plugin-flags'; -const FlagButton = ({flag, id, postAction, addItem, updateItem, addNotification}) => { +const FlagButton = ({flag, id, postAction, deleteAction, addItem, updateItem, addNotification}) => { const flagged = flag && flag.current_user; const onFlagClick = () => { if (!flagged) { @@ -14,17 +14,23 @@ const FlagButton = ({flag, id, postAction, addItem, updateItem, addNotification} updateItem(action.item_id, action.action_type, action.id, 'comments'); }); addNotification('success', lang.t('flag-notif')); + } else { + deleteAction(id, 'flag', '123', 'comments') + .then(() => { + updateItem(id, 'flag', '', 'comments'); + }); + addNotification('success', lang.t('flag-notif-remove')); } }; - return
- diff --git a/client/coral-plugin-flags/translations.json b/client/coral-plugin-flags/translations.json index 28eb9f8cb..fb2daa883 100644 --- a/client/coral-plugin-flags/translations.json +++ b/client/coral-plugin-flags/translations.json @@ -2,11 +2,13 @@ "en": { "flag": "Flag", "flagged": "Flagged", - "flag-notif": "Thank you for reporting this comment. Our moderation team has been notified and will review it shortly." + "flag-notif": "Thank you for reporting this comment. Our moderation team has been notified and will review it shortly.", + "flag-notif-remove": "Your flag has been removed." }, "es": { "flag": "Marcar", "flagged": "Marcado", - "flag-notif": "Gracias por marcar este comentario. Nuestro equipo de moderación ha sido notificado y muy pronto lo va a revisar." + "flag-notif": "Gracias por marcar este comentario. Nuestro equipo de moderación ha sido notificado y muy pronto lo va a revisar.", + "flag-notif-remove": "¡traduceme!" } } diff --git a/client/coral-plugin-likes/LikeButton.js b/client/coral-plugin-likes/LikeButton.js index ec8528793..9bdf7b84d 100644 --- a/client/coral-plugin-likes/LikeButton.js +++ b/client/coral-plugin-likes/LikeButton.js @@ -4,7 +4,7 @@ import translations from './translations.json'; const name = 'coral-plugin-flags'; -const LikeButton = ({like, id, postAction, addItem, updateItem}) => { +const LikeButton = ({like, id, postAction, deleteAction, addItem, updateItem}) => { const liked = like && like.current_user; const onLikeClick = () => { if (!liked) { @@ -13,6 +13,12 @@ const LikeButton = ({like, id, postAction, addItem, updateItem}) => { addItem({id: action.id, current_user:true, count: like ? like.count + 1 : 1}, 'actions'); updateItem(action.item_id, action.action_type, action.id, 'comments'); }); + } else { + deleteAction(id, 'like', '123', 'comments') + .then(() => { + updateItem(like.id, 'count', like.count - 1, 'actions'); + updateItem(like.id, 'current_user', false, 'actions'); + }); } }; @@ -25,7 +31,7 @@ const LikeButton = ({like, id, postAction, addItem, updateItem}) => { } thumb_up - {like && like.count} + {like && like.count > 0 && like.count}
; }; diff --git a/tests/client/coral-framework/store/itemActions.spec.js b/tests/client/coral-framework/store/itemActions.spec.js index d45212794..33fe15cab 100644 --- a/tests/client/coral-framework/store/itemActions.spec.js +++ b/tests/client/coral-framework/store/itemActions.spec.js @@ -162,6 +162,24 @@ describe('itemActions', () => { expect(err).to.be.truthy; }); }); + }); + describe('deleteAction', () => { + it ('should remove an action', () => { + fetchMock.delete('*', 'Action removed.'); + return actions.deleteAction('abc', 'flag', '123', 'comments')(store.dispatch) + .then(response => { + expect(fetchMock.calls().matched[0][0]).to.equal('/api/v1/comments/abc/actions'); + expect(response).to.equal('Action removed.'); + }); + }); + + it('should handle an error', () => { + fetchMock.post('*', 404); + return actions.postAction('abc', 'flag', '123')(store.dispatch) + .catch((err) => { + expect(err).to.be.truthy; + }); + }); }); }); From 984b2acac49d882405d90517f561ad28744f15f4 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Fri, 11 Nov 2016 22:38:15 -0300 Subject: [PATCH 11/86] =?UTF-8?q?=C3=81dding=20Traslations=20and=20Same=20?= =?UTF-8?q?Origin=20Requests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/coral-framework/actions/auth.js | 9 +++------ client/coral-framework/helpers/index.js | 13 ++++++++----- client/coral-sign-in/components/SignIn.js | 8 ++++++-- client/coral-sign-in/translations.js | 14 ++++++++++++++ 4 files changed, 31 insertions(+), 13 deletions(-) create mode 100644 client/coral-sign-in/translations.js diff --git a/client/coral-framework/actions/auth.js b/client/coral-framework/actions/auth.js index 9b1053161..fb5e8d3d7 100644 --- a/client/coral-framework/actions/auth.js +++ b/client/coral-framework/actions/auth.js @@ -1,5 +1,5 @@ import * as actions from '../constants/auth'; -import {base, handleResp} from '../helpers'; +import {base, handleResp, getInit} from '../helpers'; export const loginFacebook = () => dispatch => { dispatch(loginFacebookRequest()); @@ -30,10 +30,7 @@ export const loginFacebookCallback = (err, data) => { export const logout = () => dispatch => { dispatch(logoutRequest()); - fetch(`${base}/api/v1/auth`, { - method: 'DELETE', - credentials: 'same-origin' - }) + fetch(`${base}/auth`, getInit('DELETE')) .then(handleResp) .then(() => dispatch(dispatch(logoutSuccess()))) .catch(error => dispatch(logoutFailure(error))); @@ -44,7 +41,7 @@ const logoutRequest = () => ({ }); const logoutSuccess = () => ({ - type: actions.USER_LOGOUT_SUCCESS + type: actions.USER_LOGOUT_SUCCESS, }); const logoutFailure = (error) => ({ diff --git a/client/coral-framework/helpers/index.js b/client/coral-framework/helpers/index.js index 439ba5a8a..bccfc5a04 100644 --- a/client/coral-framework/helpers/index.js +++ b/client/coral-framework/helpers/index.js @@ -1,12 +1,15 @@ export const base = '/api/v1'; export const getInit = (method, body) => { - const headers = new Headers({ - 'Content-Type': 'application/json', - 'Accept': 'application/json' - }); + let init = { + method, + headers: new Headers({ + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }), + credentials: 'same-origin' + }; - const init = {method, headers}; if (method.toLowerCase() !== 'get') { init.body = JSON.stringify(body); } diff --git a/client/coral-sign-in/components/SignIn.js b/client/coral-sign-in/components/SignIn.js index 61f1e387e..52b6ba19d 100644 --- a/client/coral-sign-in/components/SignIn.js +++ b/client/coral-sign-in/components/SignIn.js @@ -1,13 +1,17 @@ import React from 'react'; import Button from './Button'; +import I18n from 'coral-framework/modules/i18n/i18n'; +import translations from '../translations'; + +const lang = new I18n(translations); const SignIn = ({openFacebookWindow, logout}) => (
- Logout + {lang.t('signIn.logout')}
); diff --git a/client/coral-sign-in/translations.js b/client/coral-sign-in/translations.js new file mode 100644 index 000000000..16e3a0425 --- /dev/null +++ b/client/coral-sign-in/translations.js @@ -0,0 +1,14 @@ +export default { + en: { + 'signIn': { + facebookLogin: 'Continue with Facebook', + logout: 'Logout' + } + }, + es: { + 'signIn': { + facebookLogin: 'Continuar con Facebook', + logout: 'Salir' + } + } +}; From f14353d464b184f931cdf3d061ad0198eee41660 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Sat, 12 Nov 2016 00:12:54 -0300 Subject: [PATCH 12/86] SignIn Dialog --- client/coral-framework/actions/auth.js | 8 +++ client/coral-framework/constants/auth.js | 2 + client/coral-framework/reducers/auth.js | 23 ++++--- client/coral-sign-in/components/SignIn.js | 15 ++--- .../coral-sign-in/components/SignInDialog.js | 37 +++++++++++ client/coral-sign-in/components/styles.css | 5 ++ .../containers/SignInContainer.js | 23 ++++++- client/coral-ui/components/Dialog.js | 62 +++++++++++++++++++ client/coral-ui/index.js | 1 + package.json | 17 ++++- 10 files changed, 168 insertions(+), 25 deletions(-) create mode 100644 client/coral-sign-in/components/SignInDialog.js create mode 100644 client/coral-ui/components/Dialog.js create mode 100644 client/coral-ui/index.js diff --git a/client/coral-framework/actions/auth.js b/client/coral-framework/actions/auth.js index fb5e8d3d7..805f9f412 100644 --- a/client/coral-framework/actions/auth.js +++ b/client/coral-framework/actions/auth.js @@ -1,6 +1,14 @@ import * as actions from '../constants/auth'; import {base, handleResp, getInit} from '../helpers'; +export const showSignInDialog = () => ({ + type: actions.SHOW_SIGNIN_DIALOG +}); + +export const hideSignInDialog = () => ({ + type: actions.HIDE_SIGNIN_DIALOG +}); + export const loginFacebook = () => dispatch => { dispatch(loginFacebookRequest()); window.open( diff --git a/client/coral-framework/constants/auth.js b/client/coral-framework/constants/auth.js index 7f93a51fe..c69bad344 100644 --- a/client/coral-framework/constants/auth.js +++ b/client/coral-framework/constants/auth.js @@ -7,3 +7,5 @@ export const USER_FACEBOOK_FAILURE = 'USER_FACEBOOK_FAILURE'; export const USER_LOGOUT_REQUEST = 'USER_LOGOUT_REQUEST'; export const USER_LOGOUT_SUCCESS = 'USER_LOGOUT_SUCCESS'; export const USER_LOGOUT_FAILURE = 'USER_LOGOUT_FAILURE'; +export const SHOW_SIGNIN_DIALOG = 'SHOW_SIGNIN_DIALOG'; +export const HIDE_SIGNIN_DIALOG = 'HIDE_SIGNIN_DIALOG'; \ No newline at end of file diff --git a/client/coral-framework/reducers/auth.js b/client/coral-framework/reducers/auth.js index c585ea237..a6c4369f9 100644 --- a/client/coral-framework/reducers/auth.js +++ b/client/coral-framework/reducers/auth.js @@ -1,21 +1,26 @@ import {Map} from 'immutable'; -// -//import { -// FETCH_COMMENTERS_REQUEST, -// FETCH_COMMENTERS_FAILURE, -// FETCH_COMMENTERS_SUCCESS, -// SORT_UPDATE -//} from '../constants/community'; -// + +import { + SHOW_SIGNIN_DIALOG, + HIDE_SIGNIN_DIALOG +} from '../constants/auth'; + const initialState = Map({ auth: Map(), loggedIn: false, error: '', - user: {} + user: {}, + showSignInDialog: false }); export default function community (state = initialState, action) { switch (action.type) { + case SHOW_SIGNIN_DIALOG : + return state + .set('showSignInDialog', true) + case HIDE_SIGNIN_DIALOG : + return state + .set('showSignInDialog', false) default : return state; } diff --git a/client/coral-sign-in/components/SignIn.js b/client/coral-sign-in/components/SignIn.js index 52b6ba19d..ad0fb5ca3 100644 --- a/client/coral-sign-in/components/SignIn.js +++ b/client/coral-sign-in/components/SignIn.js @@ -1,18 +1,11 @@ import React from 'react'; -import Button from './Button'; -import I18n from 'coral-framework/modules/i18n/i18n'; -import translations from '../translations'; -const lang = new I18n(translations); - -const SignIn = ({openFacebookWindow, logout}) => ( +const SignIn = ({openSignInDialog, children}) => (
- - - {lang.t('signIn.logout')} - + {children}
); diff --git a/client/coral-sign-in/components/SignInDialog.js b/client/coral-sign-in/components/SignInDialog.js new file mode 100644 index 000000000..f4f089a19 --- /dev/null +++ b/client/coral-sign-in/components/SignInDialog.js @@ -0,0 +1,37 @@ +import React, {Component} from 'react'; +import { Dialog } from 'coral-ui'; +import styles from './styles.css'; +import Button from './Button'; +import I18n from 'coral-framework/modules/i18n/i18n'; +import translations from '../translations'; +const lang = new I18n(translations); + +const SignInDialog = ({open, openFacebookWindow}) => ( + +

Sign In

+ +
+
+ + + + Forgot Password + Need an Account? Register +
+
+); + +export default SignInDialog; diff --git a/client/coral-sign-in/components/styles.css b/client/coral-sign-in/components/styles.css index 6dbbd5b2f..594b2e701 100644 --- a/client/coral-sign-in/components/styles.css +++ b/client/coral-sign-in/components/styles.css @@ -40,4 +40,9 @@ .facebook:hover { background-color: #365899; border-color: #365899; +} + +.dialog { + width: 400px; + border: none; } \ No newline at end of file diff --git a/client/coral-sign-in/containers/SignInContainer.js b/client/coral-sign-in/containers/SignInContainer.js index 72b46536e..4df96c49a 100644 --- a/client/coral-sign-in/containers/SignInContainer.js +++ b/client/coral-sign-in/containers/SignInContainer.js @@ -2,11 +2,14 @@ import React, {Component} from 'react'; import {connect} from 'react-redux'; import SignIn from '../components/SignIn'; +import SignInDialog from '../components/SignInDialog'; +import Button from '../components/Button'; import { loginFacebookCallback, loginFacebook, - logout + logout, + showSignInDialog } from '../../coral-framework/actions/auth'; class SignInContainer extends Component { @@ -14,6 +17,7 @@ class SignInContainer extends Component { super(props); this.openFacebookWindow = this.openFacebookWindow.bind(this); + this.openSignInDialog = this.openSignInDialog.bind(this); this.logout = this.logout.bind(this); } @@ -29,8 +33,23 @@ class SignInContainer extends Component { this.props.dispatch(logout()); } + openSignInDialog() { + this.props.dispatch(showSignInDialog()); + } + render() { - return ; + const {auth} = this.props; + return ( +
+ + +
+ ); } } diff --git a/client/coral-ui/components/Dialog.js b/client/coral-ui/components/Dialog.js new file mode 100644 index 000000000..54f0723e6 --- /dev/null +++ b/client/coral-ui/components/Dialog.js @@ -0,0 +1,62 @@ +import React, {Component, PropTypes} from 'react'; +import ReactDOM from 'react-dom'; +import dialogPolyfill from 'dialog-polyfill'; +import 'dialog-polyfill/dialog-polyfill.css'; + +export default class Dialog extends Component { + static propTypes = { + className: PropTypes.string, + title: PropTypes.string, + onCancel: PropTypes.func, + onClose: PropTypes.func, + open: PropTypes.bool, + style: PropTypes.object + }; + + static defaultProps = { + onCancel: e => e.preventDefault(), + onClose: e => e.preventDefault() + }; + + componentDidMount(){ + const dialog = ReactDOM.findDOMNode(this.refs.dialog); + dialogPolyfill.registerDialog(dialog); + + if (this.props.open) { + dialog.showModal(); + } + + dialog.addEventListener('close', this.props.onClose); + dialog.addEventListener('cancel', this.props.onCancel); + } + + componentDidUpdate(prevProps) { + const dialog = ReactDOM.findDOMNode(this.refs.dialog); + if (this.props.open !== prevProps.open) { + if (this.props.open) { + dialog.showModal(); + } else { + dialog.close(); + } + } + } + + componentWillUnmount() { + const dialog = ReactDOM.findDOMNode(this.refs.dialog); + dialog.removeEventListener('cancel', this.props.onCancel); + } + + render() { + const { children, className = '', onClose, onCancel, open, ...rest } = this.props; + + return ( + + {children} + + ) + } +} \ No newline at end of file diff --git a/client/coral-ui/index.js b/client/coral-ui/index.js new file mode 100644 index 000000000..3d76a5912 --- /dev/null +++ b/client/coral-ui/index.js @@ -0,0 +1 @@ +export { default as Dialog } from './components/Dialog'; \ No newline at end of file diff --git a/package.json b/package.json index 6312e51ff..4341483af 100644 --- a/package.json +++ b/package.json @@ -16,8 +16,13 @@ "config": { "pre-git": { "commit-msg": [], - "pre-commit": ["npm run lint", "npm test"], - "pre-push": ["npm test"], + "pre-commit": [ + "npm run lint", + "npm test" + ], + "pre-push": [ + "npm test" + ], "post-commit": [], "post-merge": [] } @@ -26,7 +31,12 @@ "type": "git", "url": "git+https://github.com/coralproject/talk.git" }, - "keywords": ["talk", "coral", "coralproject", "ask"], + "keywords": [ + "talk", + "coral", + "coralproject", + "ask" + ], "author": "", "license": "Apache-2.0", "bugs": { @@ -72,6 +82,7 @@ "chai-http": "^3.0.0", "copy-webpack-plugin": "^4.0.0", "css-loader": "^0.25.0", + "dialog-polyfill": "^0.4.4", "eslint": "^3.9.1", "eslint-config-postcss": "^2.0.2", "eslint-config-standard": "^6.2.1", From d6dc3a8ba1163d8c96adbdc172a0e5519c7c0e29 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Sat, 12 Nov 2016 12:21:33 -0300 Subject: [PATCH 13/86] Sign up steps and Login --- client/coral-framework/actions/auth.js | 25 +++++++ client/coral-framework/constants/auth.js | 6 +- client/coral-framework/reducers/auth.js | 13 ++-- client/coral-sign-in/components/Button.js | 4 +- client/coral-sign-in/components/SignDialog.js | 15 +++++ client/coral-sign-in/components/SignIn.js | 12 ---- .../coral-sign-in/components/SignInContent.js | 41 ++++++++++++ .../coral-sign-in/components/SignInDialog.js | 37 ---------- .../coral-sign-in/components/SignUpContent.js | 48 +++++++++++++ client/coral-sign-in/components/styles.css | 67 +++++++++++++++++-- .../containers/SignInContainer.js | 15 +++-- client/coral-sign-in/translations.js | 32 +++++++-- client/coral-ui/components/Dialog.js | 6 +- client/coral-ui/index.js | 2 +- 14 files changed, 251 insertions(+), 72 deletions(-) create mode 100644 client/coral-sign-in/components/SignDialog.js delete mode 100644 client/coral-sign-in/components/SignIn.js create mode 100644 client/coral-sign-in/components/SignInContent.js delete mode 100644 client/coral-sign-in/components/SignInDialog.js create mode 100644 client/coral-sign-in/components/SignUpContent.js diff --git a/client/coral-framework/actions/auth.js b/client/coral-framework/actions/auth.js index 805f9f412..b77b00003 100644 --- a/client/coral-framework/actions/auth.js +++ b/client/coral-framework/actions/auth.js @@ -64,3 +64,28 @@ export const loginFacebookRequest = () => ({ export const loginRequest = () => ({ type: actions.USER_SIGNIN_REQUEST }); + +const showSignInForm = () => ({ + type: actions.SHOW_SIGNIN_FORM +}); + +const showSignUpForm = () => ({ + type: actions.SHOW_SIGNUP_FORM +}); + +const newStep = (step) => ({ + type: actions.NEW_STEP, + step +}); + +export const goTo = (step) => dispatch => { + dispatch(newStep(step)); + switch (step) { + case 1 : + return dispatch(showSignInForm()); + case 2 : + return dispatch(showSignUpForm()); + default : + return dispatch(showSignInForm()); + } +}; diff --git a/client/coral-framework/constants/auth.js b/client/coral-framework/constants/auth.js index c69bad344..476e4f225 100644 --- a/client/coral-framework/constants/auth.js +++ b/client/coral-framework/constants/auth.js @@ -8,4 +8,8 @@ export const USER_LOGOUT_REQUEST = 'USER_LOGOUT_REQUEST'; export const USER_LOGOUT_SUCCESS = 'USER_LOGOUT_SUCCESS'; export const USER_LOGOUT_FAILURE = 'USER_LOGOUT_FAILURE'; export const SHOW_SIGNIN_DIALOG = 'SHOW_SIGNIN_DIALOG'; -export const HIDE_SIGNIN_DIALOG = 'HIDE_SIGNIN_DIALOG'; \ No newline at end of file +export const HIDE_SIGNIN_DIALOG = 'HIDE_SIGNIN_DIALOG'; + +export const NEW_STEP = 'NEW_STEP'; +export const SHOW_SIGNIN_FORM = 'SHOW_SIGNIN_FORM'; +export const SHOW_SIGNUP_FORM = 'SHOW_SIGNUP_FORM'; diff --git a/client/coral-framework/reducers/auth.js b/client/coral-framework/reducers/auth.js index a6c4369f9..babbe20d1 100644 --- a/client/coral-framework/reducers/auth.js +++ b/client/coral-framework/reducers/auth.js @@ -2,7 +2,8 @@ import {Map} from 'immutable'; import { SHOW_SIGNIN_DIALOG, - HIDE_SIGNIN_DIALOG + HIDE_SIGNIN_DIALOG, + NEW_STEP } from '../constants/auth'; const initialState = Map({ @@ -10,17 +11,21 @@ const initialState = Map({ loggedIn: false, error: '', user: {}, - showSignInDialog: false + showSignInDialog: false, + step: 1 }); export default function community (state = initialState, action) { switch (action.type) { case SHOW_SIGNIN_DIALOG : return state - .set('showSignInDialog', true) + .set('showSignInDialog', true); case HIDE_SIGNIN_DIALOG : return state - .set('showSignInDialog', false) + .set('showSignInDialog', false); + case NEW_STEP : + return state + .set('step', action.step); default : return state; } diff --git a/client/coral-sign-in/components/Button.js b/client/coral-sign-in/components/Button.js index fd8cc85c7..3f736ac3d 100644 --- a/client/coral-sign-in/components/Button.js +++ b/client/coral-sign-in/components/Button.js @@ -1,9 +1,9 @@ import React from 'react'; import styles from './styles.css'; -const Button = ({type = 'local', children, ...props}) => ( +const Button = ({type = 'local', children, className, ...props}) => ( - {children} - -); - -export default SignIn; diff --git a/client/coral-sign-in/components/SignInContent.js b/client/coral-sign-in/components/SignInContent.js new file mode 100644 index 000000000..f8893fb25 --- /dev/null +++ b/client/coral-sign-in/components/SignInContent.js @@ -0,0 +1,41 @@ +import React from 'react'; +import styles from './styles.css'; +import Button from './Button'; +import I18n from 'coral-framework/modules/i18n/i18n'; +import translations from '../translations'; +const lang = new I18n(translations); + +const SignInContent = ({openFacebookWindow, goTo}) => ( +
+
+

{lang.t('signIn.signIn')}

+
+
+ +
+
+

{lang.t('signIn.or')}

+
+
+
+ + +
+
+ + +
+ +
+ +
+); + +export default SignInContent; diff --git a/client/coral-sign-in/components/SignInDialog.js b/client/coral-sign-in/components/SignInDialog.js deleted file mode 100644 index f4f089a19..000000000 --- a/client/coral-sign-in/components/SignInDialog.js +++ /dev/null @@ -1,37 +0,0 @@ -import React, {Component} from 'react'; -import { Dialog } from 'coral-ui'; -import styles from './styles.css'; -import Button from './Button'; -import I18n from 'coral-framework/modules/i18n/i18n'; -import translations from '../translations'; -const lang = new I18n(translations); - -const SignInDialog = ({open, openFacebookWindow}) => ( - -

Sign In

- -
-
- - - - Forgot Password - Need an Account? Register -
-
-); - -export default SignInDialog; diff --git a/client/coral-sign-in/components/SignUpContent.js b/client/coral-sign-in/components/SignUpContent.js new file mode 100644 index 000000000..2568797a7 --- /dev/null +++ b/client/coral-sign-in/components/SignUpContent.js @@ -0,0 +1,48 @@ +import React from 'react'; +import styles from './styles.css'; +import Button from './Button'; +import I18n from 'coral-framework/modules/i18n/i18n'; +import translations from '../translations'; +const lang = new I18n(translations); + +const SignUpContent = ({goTo, openFacebookWindow}) => ( +
+
+

{lang.t('signIn.signUp')}

+
+
+ +
+
+

{lang.t('signIn.or')}

+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+
+ {lang.t('signIn.alreadyHaveAnAccount')} goTo(1)}>{lang.t('signIn.signIn')} +
+
+); + +export default SignUpContent; diff --git a/client/coral-sign-in/components/styles.css b/client/coral-sign-in/components/styles.css index 594b2e701..b289ac8fb 100644 --- a/client/coral-sign-in/components/styles.css +++ b/client/coral-sign-in/components/styles.css @@ -12,7 +12,6 @@ font-family: 'Roboto','Helvetica','Arial',sans-serif; font-size: 14px; font-weight: 500; - text-transform: uppercase; letter-spacing: 0; overflow: hidden; will-change: box-shadow,transform; @@ -24,20 +23,27 @@ text-align: center; line-height: 36px; vertical-align: middle; + width: 100%; + margin: 0; } -.local { +.type--black { + color: #E0E0E0; + background: #212121; +} + +.type--local { background: #E0E0E0; color: #212121; } -.facebook { +.type--facebook { background-color: #4267b2; border-color: #4267b2; color: rgb(255, 255, 255); } -.facebook:hover { +.type--facebook:hover { background-color: #365899; border-color: #365899; } @@ -45,4 +51,57 @@ .dialog { width: 400px; border: none; + border: none; + box-shadow: 0 9px 46px 8px rgba(0, 0, 0, 0.14), 0 11px 15px -7px rgba(0, 0, 0, 0.12), 0 24px 38px 3px rgba(0, 0, 0, 0.2); + width: 280px; +} + +.header { + margin-bottom: 20px; +} + +.header h1, .separator h1{ + text-align: center; + font-size: 1.2em; +} + +.formField label { + font-size: 1.08em; + font-weight: bold; + margin-bottom: 5px; +} + +.formField input { + width: 100%; + display: block; + border: none; + outline: none; + border: 1px solid rgba(0,0,0,.12); + padding: 10px 6px; + box-sizing: border-box; + border-radius: 2px; + margin: 5px auto; +} + +.footer { + margin: 20px auto 10px; + text-align: center; +} + +.footer span { + display: block; + margin-bottom: 5px; +} + +.footer a { + color: #2c69b6; + cursor: pointer; +} + +.socialConnections { + margin-bottom: 20px; +} + +.signInButton { + margin-top: 10px; } \ No newline at end of file diff --git a/client/coral-sign-in/containers/SignInContainer.js b/client/coral-sign-in/containers/SignInContainer.js index 4df96c49a..7c7bd7775 100644 --- a/client/coral-sign-in/containers/SignInContainer.js +++ b/client/coral-sign-in/containers/SignInContainer.js @@ -1,15 +1,15 @@ import React, {Component} from 'react'; import {connect} from 'react-redux'; -import SignIn from '../components/SignIn'; -import SignInDialog from '../components/SignInDialog'; +import SignDialog from '../components/SignDialog'; import Button from '../components/Button'; import { loginFacebookCallback, loginFacebook, logout, - showSignInDialog + showSignInDialog, + goTo, } from '../../coral-framework/actions/auth'; class SignInContainer extends Component { @@ -19,6 +19,7 @@ class SignInContainer extends Component { this.openFacebookWindow = this.openFacebookWindow.bind(this); this.openSignInDialog = this.openSignInDialog.bind(this); this.logout = this.logout.bind(this); + this.goTo = this.goTo.bind(this); } componentDidMount() { @@ -37,6 +38,10 @@ class SignInContainer extends Component { this.props.dispatch(showSignInDialog()); } + goTo(step = 1) { + this.props.dispatch(goTo(step)); + } + render() { const {auth} = this.props; return ( @@ -44,9 +49,11 @@ class SignInContainer extends Component { - ); diff --git a/client/coral-sign-in/translations.js b/client/coral-sign-in/translations.js index 16e3a0425..1f11935bd 100644 --- a/client/coral-sign-in/translations.js +++ b/client/coral-sign-in/translations.js @@ -1,14 +1,38 @@ export default { en: { 'signIn': { - facebookLogin: 'Continue with Facebook', - logout: 'Logout' + facebookSignIn: 'Sign in with Facebook', + facebookSignUp: 'Sign up with Facebook', + logout: 'Logout', + signIn: 'Sign In', + or: 'Or', + email: 'E-mail Address', + password: 'Password', + forgotYourPass: 'Forgot your password?', + needAnAccount: 'Need an account?', + register: 'Register', + signUp: 'Sign Up', + confirmPassword: 'Confirm Password', + username: 'Username', + alreadyHaveAnAccount: 'Already have an account?' } }, es: { 'signIn': { - facebookLogin: 'Continuar con Facebook', - logout: 'Salir' + facebookSignIn: 'Entrar con Facebook', + facebookSignUp: 'Regístrate con Facebook', + logout: 'Salir', + signIn: 'Entrar', + or: 'o', + email: 'E-mail', + password: 'Contraseña', + forgotYourPass: 'Has olvidado tu contraseña?', + needAnAccount: 'Necesitas una cuenta?', + register: 'Regístrate', + signUp: 'Registro', + confirmPassword: 'Confirmar Contraseña', + username: 'Usuario', + alreadyHaveAnAccount: 'Ya tienes una cuenta?' } } }; diff --git a/client/coral-ui/components/Dialog.js b/client/coral-ui/components/Dialog.js index 54f0723e6..3f6585297 100644 --- a/client/coral-ui/components/Dialog.js +++ b/client/coral-ui/components/Dialog.js @@ -47,7 +47,7 @@ export default class Dialog extends Component { } render() { - const { children, className = '', onClose, onCancel, open, ...rest } = this.props; + const {children, className = '', onClose, onCancel, open, ...rest} = this.props; // eslint-disable-line return ( {children} - ) + ); } -} \ No newline at end of file +} diff --git a/client/coral-ui/index.js b/client/coral-ui/index.js index 3d76a5912..5116a16dd 100644 --- a/client/coral-ui/index.js +++ b/client/coral-ui/index.js @@ -1 +1 @@ -export { default as Dialog } from './components/Dialog'; \ No newline at end of file +export {default as Dialog} from './components/Dialog'; From eb752e6f734fdd6a12e63dc7e55a9f05b4d82d9b Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Sat, 12 Nov 2016 12:38:02 -0300 Subject: [PATCH 14/86] Handling Views --- client/coral-framework/actions/auth.js | 30 ++++--------------- client/coral-framework/constants/auth.js | 5 ++-- client/coral-framework/reducers/auth.js | 8 ++--- client/coral-sign-in/components/SignDialog.js | 6 ++-- .../coral-sign-in/components/SignInContent.js | 4 +-- .../coral-sign-in/components/SignUpContent.js | 4 +-- .../containers/SignInContainer.js | 12 ++++---- 7 files changed, 25 insertions(+), 44 deletions(-) diff --git a/client/coral-framework/actions/auth.js b/client/coral-framework/actions/auth.js index b77b00003..0a70da7ba 100644 --- a/client/coral-framework/actions/auth.js +++ b/client/coral-framework/actions/auth.js @@ -52,7 +52,7 @@ const logoutSuccess = () => ({ type: actions.USER_LOGOUT_SUCCESS, }); -const logoutFailure = (error) => ({ +const logoutFailure = error => ({ type: actions.USER_LOGOUT_SUCCESS, error }); @@ -65,27 +65,9 @@ export const loginRequest = () => ({ type: actions.USER_SIGNIN_REQUEST }); -const showSignInForm = () => ({ - type: actions.SHOW_SIGNIN_FORM -}); +export const changeView = view => dispatch => + dispatch({ + type: actions.CHANGE_VIEW, + view + }); -const showSignUpForm = () => ({ - type: actions.SHOW_SIGNUP_FORM -}); - -const newStep = (step) => ({ - type: actions.NEW_STEP, - step -}); - -export const goTo = (step) => dispatch => { - dispatch(newStep(step)); - switch (step) { - case 1 : - return dispatch(showSignInForm()); - case 2 : - return dispatch(showSignUpForm()); - default : - return dispatch(showSignInForm()); - } -}; diff --git a/client/coral-framework/constants/auth.js b/client/coral-framework/constants/auth.js index 476e4f225..f590fce12 100644 --- a/client/coral-framework/constants/auth.js +++ b/client/coral-framework/constants/auth.js @@ -7,9 +7,8 @@ export const USER_FACEBOOK_FAILURE = 'USER_FACEBOOK_FAILURE'; export const USER_LOGOUT_REQUEST = 'USER_LOGOUT_REQUEST'; export const USER_LOGOUT_SUCCESS = 'USER_LOGOUT_SUCCESS'; export const USER_LOGOUT_FAILURE = 'USER_LOGOUT_FAILURE'; + export const SHOW_SIGNIN_DIALOG = 'SHOW_SIGNIN_DIALOG'; export const HIDE_SIGNIN_DIALOG = 'HIDE_SIGNIN_DIALOG'; -export const NEW_STEP = 'NEW_STEP'; -export const SHOW_SIGNIN_FORM = 'SHOW_SIGNIN_FORM'; -export const SHOW_SIGNUP_FORM = 'SHOW_SIGNUP_FORM'; +export const CHANGE_VIEW = 'CHANGE_VIEW'; diff --git a/client/coral-framework/reducers/auth.js b/client/coral-framework/reducers/auth.js index babbe20d1..1cc67d192 100644 --- a/client/coral-framework/reducers/auth.js +++ b/client/coral-framework/reducers/auth.js @@ -3,7 +3,7 @@ import {Map} from 'immutable'; import { SHOW_SIGNIN_DIALOG, HIDE_SIGNIN_DIALOG, - NEW_STEP + CHANGE_VIEW } from '../constants/auth'; const initialState = Map({ @@ -12,7 +12,7 @@ const initialState = Map({ error: '', user: {}, showSignInDialog: false, - step: 1 + view: 'SIGNIN' }); export default function community (state = initialState, action) { @@ -23,9 +23,9 @@ export default function community (state = initialState, action) { case HIDE_SIGNIN_DIALOG : return state .set('showSignInDialog', false); - case NEW_STEP : + case CHANGE_VIEW : return state - .set('step', action.step); + .set('view', action.view); default : return state; } diff --git a/client/coral-sign-in/components/SignDialog.js b/client/coral-sign-in/components/SignDialog.js index 713c9789b..6aadc1744 100644 --- a/client/coral-sign-in/components/SignDialog.js +++ b/client/coral-sign-in/components/SignDialog.js @@ -5,10 +5,10 @@ import styles from './styles.css'; import SignInContent from './SignInContent'; import SingUpContent from './SignUpContent'; -const SignDialog = ({open, step, ...props}) => ( +const SignDialog = ({open, view, ...props}) => ( - {step === 1 && } - {step === 2 && } + {view === 'SIGNIN' && } + {view === 'SIGNUP' && } ); diff --git a/client/coral-sign-in/components/SignInContent.js b/client/coral-sign-in/components/SignInContent.js index f8893fb25..54428dbe9 100644 --- a/client/coral-sign-in/components/SignInContent.js +++ b/client/coral-sign-in/components/SignInContent.js @@ -5,7 +5,7 @@ import I18n from 'coral-framework/modules/i18n/i18n'; import translations from '../translations'; const lang = new I18n(translations); -const SignInContent = ({openFacebookWindow, goTo}) => ( +const SignInContent = ({openFacebookWindow, changeView}) => (

{lang.t('signIn.signIn')}

@@ -33,7 +33,7 @@ const SignInContent = ({openFacebookWindow, goTo}) => (
); diff --git a/client/coral-sign-in/components/SignUpContent.js b/client/coral-sign-in/components/SignUpContent.js index 2568797a7..70fb30d06 100644 --- a/client/coral-sign-in/components/SignUpContent.js +++ b/client/coral-sign-in/components/SignUpContent.js @@ -5,7 +5,7 @@ import I18n from 'coral-framework/modules/i18n/i18n'; import translations from '../translations'; const lang = new I18n(translations); -const SignUpContent = ({goTo, openFacebookWindow}) => ( +const SignUpContent = ({changeView, openFacebookWindow}) => (

{lang.t('signIn.signUp')}

@@ -40,7 +40,7 @@ const SignUpContent = ({goTo, openFacebookWindow}) => (
- {lang.t('signIn.alreadyHaveAnAccount')} goTo(1)}>{lang.t('signIn.signIn')} + {lang.t('signIn.alreadyHaveAnAccount')} changeView('SIGNIN')}>{lang.t('signIn.signIn')}
); diff --git a/client/coral-sign-in/containers/SignInContainer.js b/client/coral-sign-in/containers/SignInContainer.js index 7c7bd7775..42a32799c 100644 --- a/client/coral-sign-in/containers/SignInContainer.js +++ b/client/coral-sign-in/containers/SignInContainer.js @@ -9,7 +9,7 @@ import { loginFacebook, logout, showSignInDialog, - goTo, + changeView, } from '../../coral-framework/actions/auth'; class SignInContainer extends Component { @@ -19,7 +19,7 @@ class SignInContainer extends Component { this.openFacebookWindow = this.openFacebookWindow.bind(this); this.openSignInDialog = this.openSignInDialog.bind(this); this.logout = this.logout.bind(this); - this.goTo = this.goTo.bind(this); + this.changeView = this.changeView.bind(this); } componentDidMount() { @@ -38,8 +38,8 @@ class SignInContainer extends Component { this.props.dispatch(showSignInDialog()); } - goTo(step = 1) { - this.props.dispatch(goTo(step)); + changeView(view) { + this.props.dispatch(changeView(view)); } render() { @@ -52,8 +52,8 @@ class SignInContainer extends Component {
); From dab93547b552269fc60e6ccecf6a28f9f7aba446 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Sat, 12 Nov 2016 12:54:27 -0300 Subject: [PATCH 15/86] Handling Views --- .../containers/SignInContainer.js | 47 ++++++++----------- 1 file changed, 20 insertions(+), 27 deletions(-) diff --git a/client/coral-sign-in/containers/SignInContainer.js b/client/coral-sign-in/containers/SignInContainer.js index 42a32799c..4a1364ca6 100644 --- a/client/coral-sign-in/containers/SignInContainer.js +++ b/client/coral-sign-in/containers/SignInContainer.js @@ -15,49 +15,42 @@ import { class SignInContainer extends Component { constructor(props) { super(props); - - this.openFacebookWindow = this.openFacebookWindow.bind(this); - this.openSignInDialog = this.openSignInDialog.bind(this); - this.logout = this.logout.bind(this); - this.changeView = this.changeView.bind(this); } componentDidMount() { - window.authCallback = loginFacebookCallback; - } - - openFacebookWindow() { - this.props.dispatch(loginFacebook()); - } - - logout() { - this.props.dispatch(logout()); - } - - openSignInDialog() { - this.props.dispatch(showSignInDialog()); - } - - changeView(view) { - this.props.dispatch(changeView(view)); + window.authCallback = this.props.loginFacebookCallback; } render() { - const {auth} = this.props; + const {auth, showSignInDialog} = this.props; return (
-
); } } -export default connect(({auth}) => ({auth}))(SignInContainer); +const mapStateToProps = state => ({ + auth: state.auth +}); + +const mapDispatchToProps = dispatch => ({ + loginFacebookCallback: () => dispatch(loginFacebookCallback()), + loginFacebook: () => dispatch(loginFacebook()), + logout: () => dispatch(logout()), + showSignInDialog: () => dispatch(showSignInDialog()), + changeView: (view) => dispatch(changeView(view)) +}); + +export default connect( + mapStateToProps, + mapDispatchToProps +)(SignInContainer); From e7d114d5dfbd6f41925ec8389fe28823e0754b2e Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Sat, 12 Nov 2016 13:27:04 -0300 Subject: [PATCH 16/86] Close and Cancel actions --- .../coral-sign-in/components/ForgotContent.js | 29 +++++++++++++++++++ client/coral-sign-in/components/SignDialog.js | 5 +++- .../coral-sign-in/components/SignInContent.js | 2 +- client/coral-sign-in/components/styles.css | 16 ++++++++++ .../containers/SignInContainer.js | 8 ++--- client/coral-sign-in/translations.js | 6 ++-- 6 files changed, 58 insertions(+), 8 deletions(-) create mode 100644 client/coral-sign-in/components/ForgotContent.js diff --git a/client/coral-sign-in/components/ForgotContent.js b/client/coral-sign-in/components/ForgotContent.js new file mode 100644 index 000000000..9f32f6933 --- /dev/null +++ b/client/coral-sign-in/components/ForgotContent.js @@ -0,0 +1,29 @@ +import React from 'react'; +import styles from './styles.css'; +import Button from './Button'; +import I18n from 'coral-framework/modules/i18n/i18n'; +import translations from '../translations'; +const lang = new I18n(translations); + +const ForgotContent = ({changeView}) => ( +
+
+

{lang.t('signIn.recoverPassword')}

+
+
+
+ + +
+ +
+
+ {lang.t('signIn.needAnAccount')} changeView('SIGNUP')}>{lang.t('signIn.register')} + {lang.t('signIn.alreadyHaveAnAccount')} changeView('SIGNIN')}>{lang.t('signIn.signIn')} +
+
+); + +export default ForgotContent; diff --git a/client/coral-sign-in/components/SignDialog.js b/client/coral-sign-in/components/SignDialog.js index 6aadc1744..e5c6f0ea4 100644 --- a/client/coral-sign-in/components/SignDialog.js +++ b/client/coral-sign-in/components/SignDialog.js @@ -4,11 +4,14 @@ import styles from './styles.css'; import SignInContent from './SignInContent'; import SingUpContent from './SignUpContent'; +import ForgotContent from './ForgotContent'; -const SignDialog = ({open, view, ...props}) => ( +const SignDialog = ({open, view, onClose, ...props}) => ( + × {view === 'SIGNIN' && } {view === 'SIGNUP' && } + {view === 'FORGOT' && } ); diff --git a/client/coral-sign-in/components/SignInContent.js b/client/coral-sign-in/components/SignInContent.js index 54428dbe9..4764f1cb2 100644 --- a/client/coral-sign-in/components/SignInContent.js +++ b/client/coral-sign-in/components/SignInContent.js @@ -32,7 +32,7 @@ const SignInContent = ({openFacebookWindow, changeView}) => (
diff --git a/client/coral-sign-in/components/styles.css b/client/coral-sign-in/components/styles.css index b289ac8fb..e7d77b85c 100644 --- a/client/coral-sign-in/components/styles.css +++ b/client/coral-sign-in/components/styles.css @@ -104,4 +104,20 @@ .signInButton { margin-top: 10px; +} + +.close { + font-size: 20px; + line-height: 14px; + top: 10px; + right: 10px; + position: absolute; + display: block; + font-weight: bold; + color: #363636; + cursor: pointer; +} + +.close:hover { + color: #6b6b6b; } \ No newline at end of file diff --git a/client/coral-sign-in/containers/SignInContainer.js b/client/coral-sign-in/containers/SignInContainer.js index 4a1364ca6..e9e32d474 100644 --- a/client/coral-sign-in/containers/SignInContainer.js +++ b/client/coral-sign-in/containers/SignInContainer.js @@ -10,6 +10,7 @@ import { logout, showSignInDialog, changeView, + hideSignInDialog } from '../../coral-framework/actions/auth'; class SignInContainer extends Component { @@ -38,16 +39,15 @@ class SignInContainer extends Component { } } -const mapStateToProps = state => ({ - auth: state.auth -}); +const mapStateToProps = ({auth}) => ({auth}); const mapDispatchToProps = dispatch => ({ loginFacebookCallback: () => dispatch(loginFacebookCallback()), loginFacebook: () => dispatch(loginFacebook()), logout: () => dispatch(logout()), showSignInDialog: () => dispatch(showSignInDialog()), - changeView: (view) => dispatch(changeView(view)) + changeView: (view) => dispatch(changeView(view)), + onClose: () => dispatch(hideSignInDialog()) }); export default connect( diff --git a/client/coral-sign-in/translations.js b/client/coral-sign-in/translations.js index 1f11935bd..a0fd7015e 100644 --- a/client/coral-sign-in/translations.js +++ b/client/coral-sign-in/translations.js @@ -14,7 +14,8 @@ export default { signUp: 'Sign Up', confirmPassword: 'Confirm Password', username: 'Username', - alreadyHaveAnAccount: 'Already have an account?' + alreadyHaveAnAccount: 'Already have an account?', + recoverPassword: 'Recover password' } }, es: { @@ -32,7 +33,8 @@ export default { signUp: 'Registro', confirmPassword: 'Confirmar Contraseña', username: 'Usuario', - alreadyHaveAnAccount: 'Ya tienes una cuenta?' + alreadyHaveAnAccount: 'Ya tienes una cuenta?', + recoverPassword: 'Recuperar contraseña' } } }; From 9341caa4d5707db26c1912e85da059962dc3137a Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Mon, 14 Nov 2016 13:04:44 -0300 Subject: [PATCH 17/86] Auth Actions --- client/coral-framework/actions/auth.js | 137 ++++++++++-------- client/coral-framework/helpers/error.js | 4 + .../helpers/{index.js => response.js} | 0 client/coral-framework/helpers/validate.js | 0 client/coral-sign-in/components/Alert.js | 0 client/coral-sign-in/components/Button.js | 13 -- client/coral-sign-in/components/FormField.js | 0 client/coral-ui/components/Button.css | 0 client/coral-ui/components/Button.js | 13 ++ client/coral-ui/components/Spinner.css | 87 +++++++++++ client/coral-ui/components/Spinner.js | 12 ++ 11 files changed, 189 insertions(+), 77 deletions(-) create mode 100644 client/coral-framework/helpers/error.js rename client/coral-framework/helpers/{index.js => response.js} (100%) create mode 100644 client/coral-framework/helpers/validate.js create mode 100644 client/coral-sign-in/components/Alert.js delete mode 100644 client/coral-sign-in/components/Button.js create mode 100644 client/coral-sign-in/components/FormField.js create mode 100644 client/coral-ui/components/Button.css create mode 100644 client/coral-ui/components/Button.js create mode 100644 client/coral-ui/components/Spinner.css create mode 100644 client/coral-ui/components/Spinner.js diff --git a/client/coral-framework/actions/auth.js b/client/coral-framework/actions/auth.js index 0a70da7ba..eb1a63f22 100644 --- a/client/coral-framework/actions/auth.js +++ b/client/coral-framework/actions/auth.js @@ -1,69 +1,9 @@ import * as actions from '../constants/auth'; -import {base, handleResp, getInit} from '../helpers'; +import {base, handleResp, getInit} from '../helpers/response'; -export const showSignInDialog = () => ({ - type: actions.SHOW_SIGNIN_DIALOG -}); - -export const hideSignInDialog = () => ({ - type: actions.HIDE_SIGNIN_DIALOG -}); - -export const loginFacebook = () => dispatch => { - dispatch(loginFacebookRequest()); - window.open( - `${base}/auth/facebook`, - 'Continue with Facebook', - 'menubar=0,resizable=0,width=500,height=500,top=200,left=500' - ); -}; - -export const loginFacebookCallback = (err, data) => { - let user; - - if (err) { - console.error(err); - return; - } - - try { - user = JSON.parse(data); - } catch (err) { - console.error('Can\'t parse the user json', err); - return; - } - - console.log('User was loaded!', user); -}; - -export const logout = () => dispatch => { - dispatch(logoutRequest()); - fetch(`${base}/auth`, getInit('DELETE')) - .then(handleResp) - .then(() => dispatch(dispatch(logoutSuccess()))) - .catch(error => dispatch(logoutFailure(error))); -}; - -const logoutRequest = () => ({ - type: actions.USER_LOGOUT_SUCCESS -}); - -const logoutSuccess = () => ({ - type: actions.USER_LOGOUT_SUCCESS, -}); - -const logoutFailure = error => ({ - type: actions.USER_LOGOUT_SUCCESS, - error -}); - -export const loginFacebookRequest = () => ({ - type: actions.USER_FACEBOOK_LOGIN_REQUEST -}); - -export const loginRequest = () => ({ - type: actions.USER_SIGNIN_REQUEST -}); +// Dialog Actions +export const showSignInDialog = () => ({type: actions.SHOW_SIGNIN_DIALOG}); +export const hideSignInDialog = () => ({type: actions.HIDE_SIGNIN_DIALOG}); export const changeView = view => dispatch => dispatch({ @@ -71,3 +11,72 @@ export const changeView = view => dispatch => view }); +export const cleanState = () => ({type: actions.CLEAN_STATE}); + +// Sign In Actions + +const signInRequest = () => ({type: actions.FETCH_SIGNIN_REQUEST}); +const signInSuccess = () => ({type: actions.FETCH_SIGNIN_SUCCESS}); +const signInFailure = () => ({type: actions.FETCH_SIGNIN_FAILURE}); + +export const fetchSignIn = () => dispatch => { + dispatch(signInRequest()); + fetch(`${base}/auth`, getInit('POST')) + .then(handleResp) + .then(() => dispatch(signInSuccess())) + .catch(error => dispatch(signInFailure(error))); +}; + +// Sign In - Facebook + +const signInFacebookRequest = () => ({type: actions.FETCH_SIGNIN_FACEBOOK_REQUEST}); +//const signInFacebookSuccess = () => ({type: actions.FETCH_SIGNIN_FACEBOOK_SUCCESS}); +//const signInFacebookFailure = () => ({type: actions.FETCH_SIGNIN_FACEBOOK_FAILURE}); + +export const fetchSignInFacebook = () => dispatch => { + dispatch(signInFacebookRequest()); + window.open( + `${base}/auth/facebook`, + 'Continue with Facebook', + 'menubar=0,resizable=0,width=500,height=500,top=200,left=500' + ); +}; + +// Sign Up Actions + +const signUpRequest = () => ({type: actions.FETCH_SIGNUP_REQUEST}); +const signUpSuccess = () => ({type: actions.FETCH_SIGNUP_SUCCESS}); +const signUpFailure = () => ({type: actions.FETCH_SIGNUP_FAILURE}); + +export const fetchSignUp = () => dispatch => { + dispatch(signUpRequest()); + fetch(`${base}/auth`, getInit('POST')) + .then(handleResp) + .then(() => dispatch(signUpSuccess())) + .catch(error => dispatch(signUpFailure(error))); +}; + +// Forgot Password Actions + +const forgotPassowordRequest = () => ({type: actions.FETCH_FORGOT_PASSWORD_REQUEST}); +const forgotPassowordSuccess = () => ({type: actions.FETCH_FORGOT_PASSWORD_SUCCESS}); +const forgotPassowordFailure = () => ({type: actions.FETCH_FORGOT_PASSWORD_FAILURE}); + +export const fetchForgotPassword = () => dispatch => { + dispatch(forgotPassowordRequest()); + fetch(`${base}/forgot`, getInit('POST')) + .then(handleResp) + .then(() => dispatch(forgotPassowordSuccess())) + .catch(error => dispatch(forgotPassowordFailure(error))); +}; + +// LogOut + +export const logout = () => dispatch => { + dispatch(signInRequest()); + fetch(`${base}/auth`, getInit('DELETE')) + .then(handleResp) + .then(() => dispatch(signInSuccess())) + .catch(error => dispatch(signInFailure(error))); +}; + diff --git a/client/coral-framework/helpers/error.js b/client/coral-framework/helpers/error.js new file mode 100644 index 000000000..6ae79a996 --- /dev/null +++ b/client/coral-framework/helpers/error.js @@ -0,0 +1,4 @@ +export default { + email: 'Not a valid E-Mail', + password: 'Password must be at least 8 characters' +}; diff --git a/client/coral-framework/helpers/index.js b/client/coral-framework/helpers/response.js similarity index 100% rename from client/coral-framework/helpers/index.js rename to client/coral-framework/helpers/response.js diff --git a/client/coral-framework/helpers/validate.js b/client/coral-framework/helpers/validate.js new file mode 100644 index 000000000..e69de29bb diff --git a/client/coral-sign-in/components/Alert.js b/client/coral-sign-in/components/Alert.js new file mode 100644 index 000000000..e69de29bb diff --git a/client/coral-sign-in/components/Button.js b/client/coral-sign-in/components/Button.js deleted file mode 100644 index 3f736ac3d..000000000 --- a/client/coral-sign-in/components/Button.js +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react'; -import styles from './styles.css'; - -const Button = ({type = 'local', children, className, ...props}) => ( - -); - -export default Button; diff --git a/client/coral-sign-in/components/FormField.js b/client/coral-sign-in/components/FormField.js new file mode 100644 index 000000000..e69de29bb diff --git a/client/coral-ui/components/Button.css b/client/coral-ui/components/Button.css new file mode 100644 index 000000000..e69de29bb diff --git a/client/coral-ui/components/Button.js b/client/coral-ui/components/Button.js new file mode 100644 index 000000000..9c0d239b7 --- /dev/null +++ b/client/coral-ui/components/Button.js @@ -0,0 +1,13 @@ +import React from 'react'; +import styles from './styles.css'; + +const Button = ({cStyle = 'local', children, className, ...props}) => ( + +); + +export default Button; diff --git a/client/coral-ui/components/Spinner.css b/client/coral-ui/components/Spinner.css new file mode 100644 index 000000000..00b7f93a5 --- /dev/null +++ b/client/coral-ui/components/Spinner.css @@ -0,0 +1,87 @@ +.container { + display: block; + text-align: center; +} + +.spinner { + -webkit-animation: rotator 1.4s linear infinite; + animation: rotator 1.4s linear infinite; +} + +@-webkit-keyframes rotator { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(270deg); + transform: rotate(270deg); + } +} + +@keyframes rotator { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(270deg); + transform: rotate(270deg); + } +} +.path { + stroke-dasharray: 187; + stroke-dashoffset: 0; + -webkit-transform-origin: center; + transform-origin: center; + -webkit-animation: dash 1.4s ease-in-out infinite, colors 5.6s ease-in-out infinite; + animation: dash 1.4s ease-in-out infinite, colors 5.6s ease-in-out infinite; +} + +@-webkit-keyframes colors { + 0% { + stroke: #f67150; + } + 100% { + stroke: #f6a47e; + } +} + +@keyframes colors { + 0% { + stroke: #f67150; + } + 100% { + stroke: #f6a47e; + } +} +@-webkit-keyframes dash { + 0% { + stroke-dashoffset: 187; + } + 50% { + stroke-dashoffset: 46.75; + -webkit-transform: rotate(135deg); + transform: rotate(135deg); + } + 100% { + stroke-dashoffset: 187; + -webkit-transform: rotate(450deg); + transform: rotate(450deg); + } +} +@keyframes dash { + 0% { + stroke-dashoffset: 187; + } + 50% { + stroke-dashoffset: 46.75; + -webkit-transform: rotate(135deg); + transform: rotate(135deg); + } + 100% { + stroke-dashoffset: 187; + -webkit-transform: rotate(450deg); + transform: rotate(450deg); + } +} diff --git a/client/coral-ui/components/Spinner.js b/client/coral-ui/components/Spinner.js new file mode 100644 index 000000000..058f1af7c --- /dev/null +++ b/client/coral-ui/components/Spinner.js @@ -0,0 +1,12 @@ +import React from 'react'; +import styles from './Spinner.css'; + +const Spinner = () => ( +
+ + + +
+); + +export default Spinner; From ef2d67a940d713e8c2c257c1a3d613d4cc66a1bf Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Mon, 14 Nov 2016 13:06:48 -0300 Subject: [PATCH 18/86] Coral-UI, Coral Auth with Reducers, Helpers and Validation --- client/coral-framework/constants/auth.js | 27 ++-- client/coral-framework/helpers/error.js | 4 +- client/coral-framework/helpers/validate.js | 6 + client/coral-framework/reducers/auth.js | 25 +++- client/coral-sign-in/components/Alert.js | 13 ++ .../coral-sign-in/components/ForgotContent.js | 8 +- client/coral-sign-in/components/FormField.js | 18 +++ client/coral-sign-in/components/SignDialog.js | 4 +- .../coral-sign-in/components/SignInContent.js | 64 ++++++--- .../coral-sign-in/components/SignUpContent.js | 89 +++++++++---- client/coral-sign-in/components/styles.css | 79 +++++------- .../containers/SignInContainer.js | 122 ++++++++++++++++-- client/coral-ui/components/Button.css | 49 +++++++ client/coral-ui/components/Button.js | 2 +- 14 files changed, 381 insertions(+), 129 deletions(-) diff --git a/client/coral-framework/constants/auth.js b/client/coral-framework/constants/auth.js index f590fce12..c48894cc5 100644 --- a/client/coral-framework/constants/auth.js +++ b/client/coral-framework/constants/auth.js @@ -1,14 +1,21 @@ -export const USER_LOGIN_REQUEST = 'USER_LOGIN_REQUEST'; -export const USER_LOGIN_SUCCESS = 'USER_LOGIN_SUCCESS'; -export const USER_LOGIN_FAILURE = 'USER_LOGIN_FAILURE'; -export const USER_FACEBOOK_LOGIN_REQUEST = 'USER_FACEBOOK_LOGIN_REQUEST'; -export const USER_FACEBOOK_SUCCESS = 'USER_FACEBOOK_SUCCESS'; -export const USER_FACEBOOK_FAILURE = 'USER_FACEBOOK_FAILURE'; -export const USER_LOGOUT_REQUEST = 'USER_LOGOUT_REQUEST'; -export const USER_LOGOUT_SUCCESS = 'USER_LOGOUT_SUCCESS'; -export const USER_LOGOUT_FAILURE = 'USER_LOGOUT_FAILURE'; +export const CHANGE_VIEW = 'CHANGE_VIEW'; +export const CLEAN_STATE = 'CLEAN_STATE'; export const SHOW_SIGNIN_DIALOG = 'SHOW_SIGNIN_DIALOG'; export const HIDE_SIGNIN_DIALOG = 'HIDE_SIGNIN_DIALOG'; -export const CHANGE_VIEW = 'CHANGE_VIEW'; +export const FETCH_SIGNUP_REQUEST = 'FETCH_SIGNUP_REQUEST'; +export const FETCH_SIGNUP_FAILURE = 'FETCH_SIGNUP_FAILURE'; +export const FETCH_SIGNUP_SUCCESS = 'FETCH_SIGNUP_SUCCESS'; + +export const FETCH_SIGNIN_REQUEST = 'FETCH_SIGNIN_REQUEST'; +export const FETCH_SIGNIN_FAILURE = 'FETCH_SIGNIN_FAILURE'; +export const FETCH_SIGNIN_SUCCESS = 'FETCH_SIGNIN_SUCCESS'; + +export const FETCH_SIGNIN_FACEBOOK_REQUEST = 'FETCH_SIGNIN_FACEBOOK_REQUEST'; +export const FETCH_SIGNIN_FACEBOOK_FAILURE = 'FETCH_SIGNIN_FACEBOOK_FAILURE'; +export const FETCH_SIGNIN_FACEBOOK_SUCCESS = 'FETCH_SIGNIN_FACEBOOK_SUCCESS'; + +export const FETCH_FORGOT_PASSWORD_REQUEST = 'FETCH_FORGOT_PASSWORD_REQUEST'; +export const FETCH_FORGOT_PASSWORD_SUCCESS = 'FETCH_FORGOT_PASSWORD_SUCCESS'; +export const FETCH_FORGOT_PASSWORD_FAILURE = 'FETCH_FORGOT_PASSWORD_FAILURE'; diff --git a/client/coral-framework/helpers/error.js b/client/coral-framework/helpers/error.js index 6ae79a996..2674bc55b 100644 --- a/client/coral-framework/helpers/error.js +++ b/client/coral-framework/helpers/error.js @@ -1,4 +1,6 @@ export default { email: 'Not a valid E-Mail', - password: 'Password must be at least 8 characters' + password: 'Password must be at least 8 characters', + username: 'Username is too short', + confirmPassword: 'Passwords do not match' }; diff --git a/client/coral-framework/helpers/validate.js b/client/coral-framework/helpers/validate.js index e69de29bb..9fda4b1c2 100644 --- a/client/coral-framework/helpers/validate.js +++ b/client/coral-framework/helpers/validate.js @@ -0,0 +1,6 @@ +export default { + email: email => (/^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/.test(email)), + password: pass => (/^(?=.{8,}).*$/.test(pass)), + confirmPassword: () => true, + username: username => (/^(?=.{3,}).*$/.test(username)) +}; diff --git a/client/coral-framework/reducers/auth.js b/client/coral-framework/reducers/auth.js index 1cc67d192..5e54d70b6 100644 --- a/client/coral-framework/reducers/auth.js +++ b/client/coral-framework/reducers/auth.js @@ -3,11 +3,16 @@ import {Map} from 'immutable'; import { SHOW_SIGNIN_DIALOG, HIDE_SIGNIN_DIALOG, - CHANGE_VIEW + CHANGE_VIEW, + CLEAN_STATE, + FETCH_SIGNIN_REQUEST, + FETCH_SIGNIN_FAILURE, + FETCH_SIGNIN_SUCCESS } from '../constants/auth'; const initialState = Map({ auth: Map(), + isLoading: false, loggedIn: false, error: '', user: {}, @@ -15,17 +20,29 @@ const initialState = Map({ view: 'SIGNIN' }); -export default function community (state = initialState, action) { +export const getEmail = state => state.auth.formData.email; + +export default function auth (state = initialState, action) { switch (action.type) { case SHOW_SIGNIN_DIALOG : return state .set('showSignInDialog', true); case HIDE_SIGNIN_DIALOG : - return state - .set('showSignInDialog', false); + return initialState; case CHANGE_VIEW : return state .set('view', action.view); + case CLEAN_STATE: + return initialState; + case FETCH_SIGNIN_REQUEST: + return state + .set('isLoading', true); + case FETCH_SIGNIN_FAILURE: + return state + .set('isLoading', false); + case FETCH_SIGNIN_SUCCESS: + return state + .set('isLoading', false); default : return state; } diff --git a/client/coral-sign-in/components/Alert.js b/client/coral-sign-in/components/Alert.js index e69de29bb..019c3456a 100644 --- a/client/coral-sign-in/components/Alert.js +++ b/client/coral-sign-in/components/Alert.js @@ -0,0 +1,13 @@ +import React from 'react'; +import styles from './styles.css'; + +const Alert = ({cStyle = 'error', children, className, ...props}) => ( +
+ {children} +
+); + +export default Alert; diff --git a/client/coral-sign-in/components/ForgotContent.js b/client/coral-sign-in/components/ForgotContent.js index 9f32f6933..7214d2bb6 100644 --- a/client/coral-sign-in/components/ForgotContent.js +++ b/client/coral-sign-in/components/ForgotContent.js @@ -1,21 +1,21 @@ import React from 'react'; import styles from './styles.css'; -import Button from './Button'; +import Button from 'coral-ui/components/Button'; import I18n from 'coral-framework/modules/i18n/i18n'; import translations from '../translations'; const lang = new I18n(translations); -const ForgotContent = ({changeView}) => ( +const ForgotContent = ({changeView, ...props}) => (

{lang.t('signIn.recoverPassword')}

-
+ {e.preventDefault(); props.fetchForgotPassword();}}>
-
diff --git a/client/coral-sign-in/components/FormField.js b/client/coral-sign-in/components/FormField.js index e69de29bb..62e50e637 100644 --- a/client/coral-sign-in/components/FormField.js +++ b/client/coral-sign-in/components/FormField.js @@ -0,0 +1,18 @@ +import React from 'react'; +import styles from './styles.css'; + +const FormField = ({className, showErrors = false, errorMsg, label, ...props}) => ( +
+ + + {showErrors && errorMsg && {errorMsg}} +
+); + +export default FormField; diff --git a/client/coral-sign-in/components/SignDialog.js b/client/coral-sign-in/components/SignDialog.js index e5c6f0ea4..242b7443a 100644 --- a/client/coral-sign-in/components/SignDialog.js +++ b/client/coral-sign-in/components/SignDialog.js @@ -6,9 +6,9 @@ import SignInContent from './SignInContent'; import SingUpContent from './SignUpContent'; import ForgotContent from './ForgotContent'; -const SignDialog = ({open, view, onClose, ...props}) => ( +const SignDialog = ({open, view, handleClose, ...props}) => ( - × + × {view === 'SIGNIN' && } {view === 'SIGNUP' && } {view === 'FORGOT' && } diff --git a/client/coral-sign-in/components/SignInContent.js b/client/coral-sign-in/components/SignInContent.js index 4764f1cb2..6a1e726c5 100644 --- a/client/coral-sign-in/components/SignInContent.js +++ b/client/coral-sign-in/components/SignInContent.js @@ -1,39 +1,65 @@ import React from 'react'; import styles from './styles.css'; -import Button from './Button'; + +import Button from 'coral-ui/components/Button'; +import FormField from './FormField'; +import Alert from './Alert'; +import Spinner from 'coral-ui/components/Spinner'; + import I18n from 'coral-framework/modules/i18n/i18n'; import translations from '../translations'; const lang = new I18n(translations); -const SignInContent = ({openFacebookWindow, changeView}) => ( +const SignInContent = ({handleChange, formData, ...props}) => (
-

{lang.t('signIn.signIn')}

+

+ {lang.t('signIn.signIn')} +

-
-

{lang.t('signIn.or')}

+

+ {lang.t('signIn.or')} +

-
-
- - -
-
- - -
- + { props.message && {props.message} } + + + + { + !props.isLoading ? + + : + + }
); diff --git a/client/coral-sign-in/components/SignUpContent.js b/client/coral-sign-in/components/SignUpContent.js index 70fb30d06..db005368f 100644 --- a/client/coral-sign-in/components/SignUpContent.js +++ b/client/coral-sign-in/components/SignUpContent.js @@ -1,46 +1,83 @@ import React from 'react'; import styles from './styles.css'; -import Button from './Button'; +import FormField from './FormField'; +import Button from 'coral-ui/components/Button'; +import Spinner from 'coral-ui/components/Spinner'; import I18n from 'coral-framework/modules/i18n/i18n'; import translations from '../translations'; const lang = new I18n(translations); -const SignUpContent = ({changeView, openFacebookWindow}) => ( +const SignUpContent = ({handleChange, formData, ...props}) => (
-

{lang.t('signIn.signUp')}

+

+ {lang.t('signIn.signUp')} +

-
-

{lang.t('signIn.or')}

+

+ {lang.t('signIn.or')} +

-
-
- - -
-
- - -
-
- - -
-
- - -
- + + + + + + + { + !props.isLoading ? + + : + + }
- {lang.t('signIn.alreadyHaveAnAccount')} changeView('SIGNIN')}>{lang.t('signIn.signIn')} + + {lang.t('signIn.alreadyHaveAnAccount')} + props.changeView('SIGNIN')}> + {lang.t('signIn.signIn')} + +
); diff --git a/client/coral-sign-in/components/styles.css b/client/coral-sign-in/components/styles.css index e7d77b85c..3800f53e1 100644 --- a/client/coral-sign-in/components/styles.css +++ b/client/coral-sign-in/components/styles.css @@ -1,53 +1,3 @@ -.button { - background: 0 0; - border: none; - border-radius: 2px; - color: #000; - display: block; - position: relative; - height: 36px; - min-width: 64px; - padding: 0 8px; - display: inline-block; - font-family: 'Roboto','Helvetica','Arial',sans-serif; - font-size: 14px; - font-weight: 500; - letter-spacing: 0; - overflow: hidden; - will-change: box-shadow,transform; - -webkit-transition: box-shadow .2s cubic-bezier(.4,0,1,1),background-color .2s cubic-bezier(.4,0,.2,1),color .2s cubic-bezier(.4,0,.2,1); - transition: box-shadow .2s cubic-bezier(.4,0,1,1),background-color .2s cubic-bezier(.4,0,.2,1),color .2s cubic-bezier(.4,0,.2,1); - outline: none; - cursor: pointer; - text-decoration: none; - text-align: center; - line-height: 36px; - vertical-align: middle; - width: 100%; - margin: 0; -} - -.type--black { - color: #E0E0E0; - background: #212121; -} - -.type--local { - background: #E0E0E0; - color: #212121; -} - -.type--facebook { - background-color: #4267b2; - border-color: #4267b2; - color: rgb(255, 255, 255); -} - -.type--facebook:hover { - background-color: #365899; - border-color: #365899; -} - .dialog { width: 400px; border: none; @@ -96,6 +46,7 @@ .footer a { color: #2c69b6; cursor: pointer; + margin: 0 5px; } .socialConnections { @@ -120,4 +71,32 @@ .close:hover { color: #6b6b6b; +} + +input.error{ + border: solid 1px #f44336; +} + +.errorMsg { + color: grey; + font-weight: 600; + padding: 3px 0 16px; +} + +.alert--success { + border: solid 1px #c0c4da; + background: #afb5c2; + color: #131314; +} + +.alert--success { + border: solid 1px #1ec00e; + background: #cbf1b8; + color: #006900; +} + +.alert--error { + border: solid 1px #f44336; + background: #F1A097; + color: #741818; } \ No newline at end of file diff --git a/client/coral-sign-in/containers/SignInContainer.js b/client/coral-sign-in/containers/SignInContainer.js index e9e32d474..b4d8a14e2 100644 --- a/client/coral-sign-in/containers/SignInContainer.js +++ b/client/coral-sign-in/containers/SignInContainer.js @@ -2,28 +2,119 @@ import React, {Component} from 'react'; import {connect} from 'react-redux'; import SignDialog from '../components/SignDialog'; -import Button from '../components/Button'; +import Button from 'coral-ui/components/Button'; + +import validate from 'coral-framework/helpers/validate'; +import error from 'coral-framework/helpers/error'; import { - loginFacebookCallback, - loginFacebook, - logout, - showSignInDialog, changeView, - hideSignInDialog + fetchSignUp, + fetchSignIn, + showSignInDialog, + hideSignInDialog, + fetchSignInFacebook, + fetchForgotPassword } from '../../coral-framework/actions/auth'; class SignInContainer extends Component { + initialState = { + formData: { + email: '', + username: '', + password: '', + confirmPassword: '' + }, + errors: {}, + showErrors: false + }; + constructor(props) { super(props); + this.state = this.initialState; + this.handleChange = this.handleChange.bind(this); + this.handleSignUp = this.handleSignUp.bind(this); + this.handleSignIn = this.handleSignIn.bind(this); + this.handleClose = this.handleClose.bind(this); + this.cleanState = this.cleanState.bind(this); + this.validation = this.validation.bind(this); } - componentDidMount() { - window.authCallback = this.props.loginFacebookCallback; + cleanState () { + this.setState(this.initialState); + } + + handleChange(e) { + const {name, value} = e.target; + this.validation(name, value, this.state.formData); + + this.setState(state => ({ + ...state, + formData: { + ...state.formData, + [name]: value + } + })); + } + + validation(name, value) { + if (!validate[name](value)) { + this.setState(state => ({ + errors: { + ...state.errors, + [name]: error[name] + } + })); + } else { + const { [name]: prop, ...errors } = this.state.errors; // eslint-disable-line + this.setState(state => ({ + ...state, + errors + })); + } + } + + isCompleted() { + const {formData} = this.state; + return !Object.keys(formData).filter(prop => !formData[prop].length).length; + } + + displayErrors(show = true) { + this.setState({ + showErrors: show + }); + } + + handleSignUp(e) { + e.preventDefault(); + this.displayErrors(); + if (this.isCompleted()) { + console.log('Is Completed!!'); + } else { + console.log('Is not Completed!!'); + } + this.props.fetchSignUp(this.state.formData); + } + + handleSignIn(e) { + e.preventDefault(); + this.displayErrors(); + this.props.fetchSignIn(this.state.formData); + } + + handleClose() { + this.cleanState(); + this.props.hideSignInDialog(); + } + + changeView(view) { + this.cleanState(); + this.props.changeView(view); } render() { const {auth, showSignInDialog} = this.props; + const {errors, showErrors, formData} = this.state; return (
@@ -42,12 +139,13 @@ class SignInContainer extends Component { const mapStateToProps = ({auth}) => ({auth}); const mapDispatchToProps = dispatch => ({ - loginFacebookCallback: () => dispatch(loginFacebookCallback()), - loginFacebook: () => dispatch(loginFacebook()), - logout: () => dispatch(logout()), + fetchSignUp: (formData) => dispatch(fetchSignUp(formData)), + fetchSignIn: (formData) => dispatch(fetchSignIn(formData)), + fetchSignInFacebook: () => dispatch(fetchSignInFacebook()), + fetchForgotPassword: (formData) => dispatch(fetchForgotPassword(formData)), showSignInDialog: () => dispatch(showSignInDialog()), changeView: (view) => dispatch(changeView(view)), - onClose: () => dispatch(hideSignInDialog()) + handleClose: () => dispatch(hideSignInDialog()) }); export default connect( diff --git a/client/coral-ui/components/Button.css b/client/coral-ui/components/Button.css index e69de29bb..150f1df7b 100644 --- a/client/coral-ui/components/Button.css +++ b/client/coral-ui/components/Button.css @@ -0,0 +1,49 @@ +.button { + background: 0 0; + border: none; + border-radius: 2px; + color: #000; + display: block; + position: relative; + height: 36px; + min-width: 64px; + padding: 0 8px; + display: inline-block; + font-family: 'Roboto','Helvetica','Arial',sans-serif; + font-size: 14px; + font-weight: 500; + letter-spacing: 0; + overflow: hidden; + will-change: box-shadow,transform; + -webkit-transition: box-shadow .2s cubic-bezier(.4,0,1,1),background-color .2s cubic-bezier(.4,0,.2,1),color .2s cubic-bezier(.4,0,.2,1); + transition: box-shadow .2s cubic-bezier(.4,0,1,1),background-color .2s cubic-bezier(.4,0,.2,1),color .2s cubic-bezier(.4,0,.2,1); + outline: none; + cursor: pointer; + text-decoration: none; + text-align: center; + line-height: 36px; + vertical-align: middle; + width: 100%; + margin: 0; +} + +.type--black { + color: #E0E0E0; + background: #212121; +} + +.type--local { + background: #E0E0E0; + color: #212121; +} + +.type--facebook { + background-color: #4267b2; + border-color: #4267b2; + color: rgb(255, 255, 255); +} + +.type--facebook:hover { + background-color: #365899; + border-color: #365899; +} \ No newline at end of file diff --git a/client/coral-ui/components/Button.js b/client/coral-ui/components/Button.js index 9c0d239b7..13ea46349 100644 --- a/client/coral-ui/components/Button.js +++ b/client/coral-ui/components/Button.js @@ -1,5 +1,5 @@ import React from 'react'; -import styles from './styles.css'; +import styles from './Button.css'; const Button = ({cStyle = 'local', children, className, ...props}) => ( + { canPost && ( + + ) + }
; } diff --git a/client/coral-sign-in/components/UserBox.js b/client/coral-sign-in/components/UserBox.js new file mode 100644 index 000000000..17c8453fb --- /dev/null +++ b/client/coral-sign-in/components/UserBox.js @@ -0,0 +1,13 @@ +import React from 'react'; +import styles from './styles.css'; + +const UserBox = ({className, user, logout, ...props}) => ( +
+ Signed in as {user.displayName}. Not you? Sign out +
+); + +export default UserBox; diff --git a/client/coral-sign-in/components/styles.css b/client/coral-sign-in/components/styles.css index 3800f53e1..3a83d0985 100644 --- a/client/coral-sign-in/components/styles.css +++ b/client/coral-sign-in/components/styles.css @@ -99,4 +99,10 @@ input.error{ border: solid 1px #f44336; background: #F1A097; color: #741818; +} + +.userBox a { + color: #2c69b6; + cursor: pointer; + margin: 0 5px; } \ No newline at end of file From 688fbd0bf68aa2e0c0290e4bf415e5ca4b93c330 Mon Sep 17 00:00:00 2001 From: David Jay Date: Mon, 14 Nov 2016 16:40:04 -0500 Subject: [PATCH 27/86] Sending asset_url to backend and retreiving stream with asset. --- .../coral-embed-stream/src/CommentStream.js | 22 +++++-------------- client/coral-framework/store/actions/items.js | 11 +++++----- .../CommentCount.js | 2 +- routes/api/stream/index.js | 2 +- .../coral-framework/store/itemActions.spec.js | 15 ++++++++----- 5 files changed, 21 insertions(+), 31 deletions(-) diff --git a/client/coral-embed-stream/src/CommentStream.js b/client/coral-embed-stream/src/CommentStream.js index 9a1910105..5fb48d0a0 100644 --- a/client/coral-embed-stream/src/CommentStream.js +++ b/client/coral-embed-stream/src/CommentStream.js @@ -76,28 +76,16 @@ class CommentStream extends Component { componentDidMount () { // Set up messaging between embedded Iframe an parent component // Using recommended Pym init code which violates .eslint standards - new Pym.Child({polling: 500}); - this.props.getStream('assetTest'); + const pym = new Pym.Child({polling: 100}); + console.log(pym); + this.props.getStream('http://www.test.com'); } render () { - if (Object.keys(this.props.items).length === 0) { - // Loading mock asset - this.props.postItem({ - comments: [], - url: 'http://coralproject.net' - }, 'asset', 'assetTest'); - - // Loading mock user - this.props.postItem({name: 'Ban Ki-Moon'}, 'user', 'user_8989') - .then((id) => { - this.props.setLoggedInUser(id); - }); - } // TODO: Replace teststream id with id from params - const rootItemId = 'assetTest'; + const rootItemId = this.props.items.assets && Object.keys(this.props.items.assets)[0]; const rootItem = this.props.items.assets && this.props.items.assets[rootItemId]; return
{ @@ -117,7 +105,7 @@ class CommentStream extends Component { reply={false}/>
{ - rootItem.comments.map((commentId) => { + rootItem.comments && rootItem.comments.map((commentId) => { const comment = this.props.items.comments[commentId]; return

diff --git a/client/coral-framework/store/actions/items.js b/client/coral-framework/store/actions/items.js index 648a1d95e..eea3315b0 100644 --- a/client/coral-framework/store/actions/items.js +++ b/client/coral-framework/store/actions/items.js @@ -77,9 +77,9 @@ export const appendItemArray = (id, property, value, add_to_front, item_type) => * @dispatches * A set of items to the item store */ -export function getStream (assetId) { +export function getStream (assetUrl) { return (dispatch) => { - return fetch(`/api/v1/stream?asset_id=${assetId}`) + return fetch(`/api/v1/stream?asset_url=${encodeURIComponent(assetUrl)}`) .then( response => { return response.ok ? response.json() : Promise.reject(`${response.status} ${response.statusText}`); @@ -95,6 +95,8 @@ export function getStream (assetId) { } } + const assetId = json.assets[0].id; + /* Sort comments by date*/ json.comments.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()); const rels = json.comments.reduce((h, item) => { @@ -112,10 +114,7 @@ export function getStream (assetId) { return h; }, {rootComments: [], childComments: {}}); - dispatch(addItem({ - id: assetId, - comments: rels.rootComments, - }, 'assets')); + dispatch(updateItem(assetId, 'comments', rels.rootComments, 'assets')); const childKeys = Object.keys(rels.childComments); for (let i = 0; i < childKeys.length; i++ ) { diff --git a/client/coral-plugin-comment-count/CommentCount.js b/client/coral-plugin-comment-count/CommentCount.js index 6786d5c74..7a27c1982 100644 --- a/client/coral-plugin-comment-count/CommentCount.js +++ b/client/coral-plugin-comment-count/CommentCount.js @@ -5,7 +5,7 @@ const name = 'coral-plugin-comment-count'; const CommentCount = ({items, id}) => { let count = 0; - if (items.assets[id]) { + if (items.assets[id] && items.assets[id].comments) { count += items.assets[id].comments.length; } const itemKeys = Object.keys(items.comments); diff --git a/routes/api/stream/index.js b/routes/api/stream/index.js index db1d2bc36..b093f0d31 100644 --- a/routes/api/stream/index.js +++ b/routes/api/stream/index.js @@ -16,7 +16,7 @@ router.get('/', (req, res, next) => { // Get the asset_id for this url (or create it if it doesn't exist) Promise.all([ - Asset.findOrCreateByUrl(req.query.asset_url), + Asset.findOrCreateByUrl(decodeURIComponent(req.query.asset_url)), Setting.getModerationSetting() ]) .then(([asset, {moderation}]) => { diff --git a/tests/client/coral-framework/store/itemActions.spec.js b/tests/client/coral-framework/store/itemActions.spec.js index 33fe15cab..d6ce0c970 100644 --- a/tests/client/coral-framework/store/itemActions.spec.js +++ b/tests/client/coral-framework/store/itemActions.spec.js @@ -18,8 +18,11 @@ describe('itemActions', () => { }); describe('getStream', () => { - const rootId = '1234'; + const assetUrl = 'http://www.test.com'; const response = { + assets: [{ + id: '1234', url: assetUrl + }], comments: [ {body: 'stuff', id: '123'}, {body: 'morestuff', id: '456'} @@ -42,17 +45,17 @@ describe('itemActions', () => { it('should get an stream from an asset_id and send the appropriate dispatches', () => { fetchMock.get('*', JSON.stringify(response)); - return actions.getStream(rootId)(store.dispatch) + return actions.getStream(assetUrl)(store.dispatch) .then((res) => { - expect(fetchMock.calls().matched[0][0]).to.equal('/api/v1/stream?asset_id=1234'); + expect(fetchMock.calls().matched[0][0]).to.equal('/api/v1/stream?asset_url=http%3A%2F%2Fwww.test.com'); expect(res).to.deep.equal(response); - expect(store.getActions()[0]).to.deep.equal({ + expect(store.getActions()[1]).to.deep.equal({ type: actions.ADD_ITEM, item: response.comments[0], item_type: 'comments', id: '123' }); - expect(store.getActions()[1]).to.deep.equal({ + expect(store.getActions()[2]).to.deep.equal({ type: actions.ADD_ITEM, item: response.comments[1], item_type: 'comments', @@ -62,7 +65,7 @@ describe('itemActions', () => { }); it('should handle an error', () => { fetchMock.get('*', 404); - return actions.getStream(rootId)(store.dispatch) + return actions.getStream(assetUrl)(store.dispatch) .catch((err) => { expect(err).to.be.truthy; }); From 9c41adf224f59cc3db1a175127758c3a82609442 Mon Sep 17 00:00:00 2001 From: Dan Zajdband Date: Mon, 14 Nov 2016 16:44:21 -0500 Subject: [PATCH 28/86] Added enum check for the user model --- models/user.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/models/user.js b/models/user.js index c0a846f1a..12f939e72 100644 --- a/models/user.js +++ b/models/user.js @@ -24,7 +24,10 @@ const UserSchema = new mongoose.Schema({ required: true } }], - roles: [String] + roles: { + type: [{ type: String, enum: ['admin', 'moderator'] }], + + } }, { timestamps: { createdAt: 'created_at', From 69ea37a520a220f9b185c2d55287bbfcf2a8470d Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Mon, 14 Nov 2016 18:48:08 -0300 Subject: [PATCH 29/86] removing mock --- client/coral-embed-stream/src/CommentStream.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/client/coral-embed-stream/src/CommentStream.js b/client/coral-embed-stream/src/CommentStream.js index 3fa989dcc..9728d07c8 100644 --- a/client/coral-embed-stream/src/CommentStream.js +++ b/client/coral-embed-stream/src/CommentStream.js @@ -38,7 +38,6 @@ const mapDispatchToProps = (dispatch) => ({ getStream: (rootId) => dispatch(getStream(rootId)), addNotification: (type, text) => dispatch(addNotification(type, text)), clearNotification: () => dispatch(clearNotification()), - setLoggedInUser: (user_id) => dispatch(setLoggedInUser(user_id)), postAction: (item, action, user, itemType) => dispatch(postAction(item, action, user, itemType)), appendItemArray: (item, property, value, addToFront, itemType) => dispatch(appendItemArray(item, property, value, addToFront, itemType)), @@ -68,11 +67,11 @@ class CommentStream extends Component { url: 'http://coralproject.net' }, 'asset', 'assetTest'); - // Loading mock user - this.props.postItem({name: 'Ban Ki-Moon'}, 'user', 'user_8989') - .then((id) => { - this.props.setLoggedInUser(id); - }); + // Loading mock user + //this.props.postItem({name: 'Ban Ki-Moon'}, 'user', 'user_8989') + // .then((id) => { + // this.props.setLoggedInUser(id); + // }); } // TODO: Replace teststream id with id from params From 16fa3e46f56af90ebf12f5e8fa1508070d94649b Mon Sep 17 00:00:00 2001 From: David Jay Date: Mon, 14 Nov 2016 16:51:54 -0500 Subject: [PATCH 30/86] Loading comment stream based on parent URL. --- client/coral-embed-stream/src/CommentStream.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/client/coral-embed-stream/src/CommentStream.js b/client/coral-embed-stream/src/CommentStream.js index 5fb48d0a0..aa58b5103 100644 --- a/client/coral-embed-stream/src/CommentStream.js +++ b/client/coral-embed-stream/src/CommentStream.js @@ -77,8 +77,7 @@ class CommentStream extends Component { // Set up messaging between embedded Iframe an parent component // Using recommended Pym init code which violates .eslint standards const pym = new Pym.Child({polling: 100}); - console.log(pym); - this.props.getStream('http://www.test.com'); + this.props.getStream(pym.parentUrl); } render () { From e53ac55e6b242272e39350ba76c74dc6bad346d5 Mon Sep 17 00:00:00 2001 From: Dan Zajdband Date: Mon, 14 Nov 2016 17:03:31 -0500 Subject: [PATCH 31/86] Linting --- client/coral-admin/src/actions/community.js | 9 ++++----- client/coral-admin/src/containers/Community/Table.js | 12 ++++++------ client/coral-admin/src/reducers/community.js | 4 ++-- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/client/coral-admin/src/actions/community.js b/client/coral-admin/src/actions/community.js index d35cf0614..7a4112f8b 100644 --- a/client/coral-admin/src/actions/community.js +++ b/client/coral-admin/src/actions/community.js @@ -42,9 +42,8 @@ export const newPage = () => ({ }); export const setRole = (id, role) => dispatch => { - return fetch(`${base}/user/${id}/role`, getInit('POST', { role })) + return fetch(`${base}/user/${id}/role`, getInit('POST', {role})) .then(() => { - return dispatch({ type: SET_ROLE, id, role }); - }) - -} + return dispatch({type: SET_ROLE, id, role}); + }); +}; diff --git a/client/coral-admin/src/containers/Community/Table.js b/client/coral-admin/src/containers/Community/Table.js index 66dbc894b..97848bc9a 100644 --- a/client/coral-admin/src/containers/Community/Table.js +++ b/client/coral-admin/src/containers/Community/Table.js @@ -1,10 +1,10 @@ -import React, { Component } from 'react'; -import { connect } from 'react-redux'; -import { SelectField, Option } from 'react-mdl-selectfield'; +import React, {Component} from 'react'; +import {connect} from 'react-redux'; +import {SelectField, Option} from 'react-mdl-selectfield'; import styles from './Community.css'; import I18n from 'coral-framework/i18n/i18n'; import translations from '../../translations'; -import { setRole } from '../../actions/community'; +import {setRole} from '../../actions/community'; const lang = new I18n(translations); @@ -20,7 +20,7 @@ class Table extends Component { } render () { - const { headers, commenters, onHeaderClickHandler } = this.props; + const {headers, commenters, onHeaderClickHandler} = this.props; return ( @@ -63,4 +63,4 @@ class Table extends Component { } } -export default connect(state => ({ commenters: state.community.get('commenters') }))(Table); +export default connect(state => ({commenters: state.community.get('commenters')}))(Table); diff --git a/client/coral-admin/src/reducers/community.js b/client/coral-admin/src/reducers/community.js index 3421a079c..d8fe88dbc 100644 --- a/client/coral-admin/src/reducers/community.js +++ b/client/coral-admin/src/reducers/community.js @@ -38,11 +38,11 @@ export default function community (state = initialState, action) { }) .set('commenters', commenters); // Sets to normal array } - case SET_ROLE: + case SET_ROLE : const commenters = state.get('commenters'); const idx = commenters.findIndex(el => el.id === action.id); - commenters[idx].roles[0] = action.role + commenters[idx].roles[0] = action.role; return state.set('commenters', commenters); case SORT_UPDATE : return state From 44292a81ad86844c22bab6a028ec90c52fdd33b5 Mon Sep 17 00:00:00 2001 From: David Jay Date: Mon, 14 Nov 2016 17:09:20 -0500 Subject: [PATCH 32/86] Moving init and responsehandler to shared functions, updating all routes to return valid JSON. --- client/coral-framework/store/actions/items.js | 82 ++++++------------- routes/api/comments/index.js | 4 +- .../coral-framework/store/itemActions.spec.js | 5 +- 3 files changed, 32 insertions(+), 59 deletions(-) diff --git a/client/coral-framework/store/actions/items.js b/client/coral-framework/store/actions/items.js index 648a1d95e..2e7dca70f 100644 --- a/client/coral-framework/store/actions/items.js +++ b/client/coral-framework/store/actions/items.js @@ -8,6 +8,23 @@ export const ADD_ITEM = 'ADD_ITEM'; export const UPDATE_ITEM = 'UPDATE_ITEM'; export const APPEND_ITEM_ARRAY = 'APPEND_ITEM_ARRAY'; +const getInit = (method, body) => { + const headers = { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }; + + const init = {method, headers}; + if (method.toLowerCase() !== 'get') { + init.body = JSON.stringify(body); + } + + return init; +}; + +const responseHandler = response => { + return response.ok ? response.json() : Promise.reject(`${response.status} ${response.statusText}`); +}; /** * Action creators */ @@ -79,12 +96,8 @@ export const appendItemArray = (id, property, value, add_to_front, item_type) => */ export function getStream (assetId) { return (dispatch) => { - return fetch(`/api/v1/stream?asset_id=${assetId}`) - .then( - response => { - return response.ok ? response.json() : Promise.reject(`${response.status} ${response.statusText}`); - } - ) + return fetch(`/api/v1/stream?asset_id=${assetId}`, getInit('GET')) + .then(responseHandler) .then((json) => { /* Add items to the store */ @@ -148,13 +161,8 @@ export function getStream (assetId) { export function getItemsArray (ids) { return (dispatch) => { - return fetch(`/v1/item/${ids}`) - .then( - response => { - return response.ok ? response.json() - : Promise.reject(`${response.status } ${ response.statusText}`); - } - ) + return fetch(`/v1/item/${ids}`, getInit('GET')) + .then(responseHandler) .then((json) => { for (let i = 0; i < json.items.length; i++) { dispatch(addItem(json.items[i])); @@ -183,20 +191,8 @@ export function postItem (item, type, id) { if (id) { item.id = id; } - let options = { - method: 'POST', - body: JSON.stringify(item), - headers: { - 'Content-Type':'application/json' - } - }; - return fetch(`/api/v1/${type}`, options) - .then( - response => { - return response.ok ? response.json() - : Promise.reject(`${response.status} ${response.statusText}`); - } - ) + return fetch(`/api/v1/${type}`, getInit('POST', item)) + .then(responseHandler) .then((json) => { dispatch(addItem({...item, id:json.id}, type)); return json.id; @@ -227,21 +223,9 @@ export function postAction (item_id, action_type, user_id, item_type) { action_type, user_id }; - const options = { - method: 'POST', - headers: { - 'Content-Type':'application/json' - }, - body: JSON.stringify(action) - }; - return fetch(`/api/v1/${item_type}/${item_id}/actions`, options) - .then( - response => { - return response.ok ? response.json() - : Promise.reject(`${response.status} ${response.statusText}`); - } - ); + return fetch(`/api/v1/${item_type}/${item_id}/actions`, getInit('POST', action)) + .then(responseHandler); }; } @@ -266,20 +250,8 @@ export function deleteAction (item_id, action_type, user_id, item_type) { action_type, user_id }; - const options = { - method: 'DELETE', - headers: { - 'Content-Type':'application/json' - }, - body: JSON.stringify(action) - }; - return fetch(`/api/v1/${item_type}/${item_id}/actions`, options) - .then( - response => { - return response.ok ? response.text() - : Promise.reject(`${response.status} ${response.statusText}`); - } - ); + return fetch(`/api/v1/${item_type}/${item_id}/actions`, getInit('DELETE', action)) + .then(responseHandler); }; } diff --git a/routes/api/comments/index.js b/routes/api/comments/index.js index eabb1be7b..4954563cb 100644 --- a/routes/api/comments/index.js +++ b/routes/api/comments/index.js @@ -126,7 +126,7 @@ router.delete('/:comment_id', (req, res, next) => { Comment .removeById(req.params.comment_id) .then(() => { - res.status(201).send('OK. Removed'); + res.status(201).send({}); }) .catch(error => { next(error); @@ -137,7 +137,7 @@ router.delete('/:comment_id/actions', (req, res, next) => { Comment .removeAction(req.params.comment_id, req.body.user_id, req.body.action_type) .then(() => { - res.status(201).send('OK. Removed'); + res.status(201).sent({}); }) .catch(error => { next(error); diff --git a/tests/client/coral-framework/store/itemActions.spec.js b/tests/client/coral-framework/store/itemActions.spec.js index 33fe15cab..4dbba5e82 100644 --- a/tests/client/coral-framework/store/itemActions.spec.js +++ b/tests/client/coral-framework/store/itemActions.spec.js @@ -119,6 +119,7 @@ describe('itemActions', () => { { method: 'POST', headers: { + 'Accept': 'application/json', 'Content-Type':'application/json' }, body: JSON.stringify(item.data) @@ -166,11 +167,11 @@ describe('itemActions', () => { describe('deleteAction', () => { it ('should remove an action', () => { - fetchMock.delete('*', 'Action removed.'); + fetchMock.delete('*', {}); return actions.deleteAction('abc', 'flag', '123', 'comments')(store.dispatch) .then(response => { expect(fetchMock.calls().matched[0][0]).to.equal('/api/v1/comments/abc/actions'); - expect(response).to.equal('Action removed.'); + expect(response).to.deep.equal({}); }); }); From 14405b22aaa44b32e404fb93eaa92c29d8f94c36 Mon Sep 17 00:00:00 2001 From: David Jay Date: Mon, 14 Nov 2016 20:02:01 -0500 Subject: [PATCH 33/86] Stripping out protocol and query from asset url. --- client/coral-embed-stream/src/CommentStream.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/coral-embed-stream/src/CommentStream.js b/client/coral-embed-stream/src/CommentStream.js index aa58b5103..06253b72f 100644 --- a/client/coral-embed-stream/src/CommentStream.js +++ b/client/coral-embed-stream/src/CommentStream.js @@ -77,7 +77,8 @@ class CommentStream extends Component { // Set up messaging between embedded Iframe an parent component // Using recommended Pym init code which violates .eslint standards const pym = new Pym.Child({polling: 100}); - this.props.getStream(pym.parentUrl); + const path = /https?\:\/\/([^?]+)/.exec(pym.parentUrl)[1]; + this.props.getStream(path); } render () { From 366e072280cdf89a1bd7fc103f500e485f0b6f5e Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Mon, 14 Nov 2016 23:35:33 -0300 Subject: [PATCH 34/86] Linting Issues --- client/coral-admin/src/reducers/community.js | 3 ++- models/user.js | 7 +++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/client/coral-admin/src/reducers/community.js b/client/coral-admin/src/reducers/community.js index d8fe88dbc..a42252805 100644 --- a/client/coral-admin/src/reducers/community.js +++ b/client/coral-admin/src/reducers/community.js @@ -38,12 +38,13 @@ export default function community (state = initialState, action) { }) .set('commenters', commenters); // Sets to normal array } - case SET_ROLE : + case SET_ROLE : { const commenters = state.get('commenters'); const idx = commenters.findIndex(el => el.id === action.id); commenters[idx].roles[0] = action.role; return state.set('commenters', commenters); + } case SORT_UPDATE : return state .set('field', action.sort.field) diff --git a/models/user.js b/models/user.js index 12f939e72..c6197a220 100644 --- a/models/user.js +++ b/models/user.js @@ -24,10 +24,9 @@ const UserSchema = new mongoose.Schema({ required: true } }], - roles: { - type: [{ type: String, enum: ['admin', 'moderator'] }], - - } + roles: { + type: [{type: String, enum: ['admin', 'moderator']}] + } }, { timestamps: { createdAt: 'created_at', From 4fd41704c090d57f4bf903489db464de0f608891 Mon Sep 17 00:00:00 2001 From: David Jay Date: Tue, 15 Nov 2016 12:31:30 -0500 Subject: [PATCH 35/86] Moving preview to embed code. --- views/embed-stream.ejs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/views/embed-stream.ejs b/views/embed-stream.ejs index 47bda6bee..36f7ce43b 100644 --- a/views/embed-stream.ejs +++ b/views/embed-stream.ejs @@ -17,7 +17,12 @@ -
- +
+ + + + From 27261cc515942d8cacafcbcfc73ef0a6f970a700 Mon Sep 17 00:00:00 2001 From: Dan Zajdband Date: Tue, 15 Nov 2016 12:37:05 -0500 Subject: [PATCH 36/86] Added fix for returning a new array --- client/coral-admin/src/reducers/community.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/coral-admin/src/reducers/community.js b/client/coral-admin/src/reducers/community.js index d8fe88dbc..215063f0f 100644 --- a/client/coral-admin/src/reducers/community.js +++ b/client/coral-admin/src/reducers/community.js @@ -43,7 +43,7 @@ export default function community (state = initialState, action) { const idx = commenters.findIndex(el => el.id === action.id); commenters[idx].roles[0] = action.role; - return state.set('commenters', commenters); + return state.set('commenters', commenters.map(id => id)); case SORT_UPDATE : return state .set('field', action.sort.field) From 2438161ad957a728d81d0b5c6ec8926203905845 Mon Sep 17 00:00:00 2001 From: Dan Zajdband Date: Tue, 15 Nov 2016 12:54:25 -0500 Subject: [PATCH 37/86] Removed extra return --- client/coral-admin/src/reducers/community.js | 1 - 1 file changed, 1 deletion(-) diff --git a/client/coral-admin/src/reducers/community.js b/client/coral-admin/src/reducers/community.js index 7ff8514fd..81a09a1cc 100644 --- a/client/coral-admin/src/reducers/community.js +++ b/client/coral-admin/src/reducers/community.js @@ -44,7 +44,6 @@ export default function community (state = initialState, action) { commenters[idx].roles[0] = action.role; return state.set('commenters', commenters.map(id => id)); - return state.set('commenters', commenters); } case SORT_UPDATE : return state From 08d672ed4655cda257ec0c44a78c6ce2648ea533 Mon Sep 17 00:00:00 2001 From: gaba Date: Tue, 15 Nov 2016 10:11:37 -0800 Subject: [PATCH 38/86] Changes model and add admin. --- .../coral-admin/src/containers/Configure.css | 6 +++++ .../coral-admin/src/containers/Configure.js | 24 ++++++++++++++++- .../coral-embed-stream/src/CommentStream.js | 4 +++ client/coral-embed-stream/style/default.css | 11 ++++++++ client/coral-plugin-infobox/InfoBox.js | 11 ++++++++ .../__tests__/infoBox.spec.js | 26 +++++++++++++++++++ models/setting.js | 12 ++++++++- tests/models/setting.js | 12 +++++++-- 8 files changed, 102 insertions(+), 4 deletions(-) create mode 100644 client/coral-plugin-infobox/InfoBox.js create mode 100644 client/coral-plugin-infobox/__tests__/infoBox.spec.js diff --git a/client/coral-admin/src/containers/Configure.css b/client/coral-admin/src/containers/Configure.css index 98cb0e254..c832e96c1 100644 --- a/client/coral-admin/src/containers/Configure.css +++ b/client/coral-admin/src/containers/Configure.css @@ -23,6 +23,12 @@ cursor: pointer; } +.configSettingInfoBox { + border: 1px solid #ccc; + border-radius: 4px; + margin-bottom: 10px; +} + .configSettingEmbed { border: 1px solid #ccc; border-radius: 4px; diff --git a/client/coral-admin/src/containers/Configure.js b/client/coral-admin/src/containers/Configure.js index 229c4e948..0dff4cf9a 100644 --- a/client/coral-admin/src/containers/Configure.js +++ b/client/coral-admin/src/containers/Configure.js @@ -7,7 +7,7 @@ import { ListItem, ListItemContent, ListItemAction, - //Textfield, + Textfield, Checkbox, Button, Icon @@ -36,6 +36,16 @@ class Configure extends React.Component { this.props.dispatch(updateSettings({moderation})); } + updateInfoBoxEnable () { + const infoboxEnable = this.props.settings.infoBoxEnable; + this.props.dispatch(updateSettings({infoboxEnable})); + } + + updateInfoBoxContent () { + const infoboxContent = this.props.settings.infoBoxContent; + this.props.dispatch(updateSettings({infoboxContent})); + } + saveSettings () { this.props.dispatch(saveSettingsToServer()); } @@ -50,6 +60,18 @@ class Configure extends React.Component { Enable pre-moderation + + + + + + {/* diff --git a/client/coral-embed-stream/src/CommentStream.js b/client/coral-embed-stream/src/CommentStream.js index bfccf7cfc..004f5b642 100644 --- a/client/coral-embed-stream/src/CommentStream.js +++ b/client/coral-embed-stream/src/CommentStream.js @@ -7,6 +7,7 @@ import { } from '../../coral-framework'; import {connect} from 'react-redux'; import CommentBox from '../../coral-plugin-commentbox/CommentBox'; +import InfoBox from '../../coral-plugin-infobox/InfoBox'; import Content from '../../coral-plugin-commentcontent/CommentContent'; import PubDate from '../../coral-plugin-pubdate/PubDate'; import Count from '../../coral-plugin-comment-count/CommentCount'; @@ -100,6 +101,9 @@ class CommentStream extends Component { rootItem ?
+ diff --git a/client/coral-embed-stream/style/default.css b/client/coral-embed-stream/style/default.css index e1c730bfc..08b1d8041 100644 --- a/client/coral-embed-stream/style/default.css +++ b/client/coral-embed-stream/style/default.css @@ -49,6 +49,17 @@ hr { font-weight: bold; } +/* Info Box Styles */ +.coral-plugin-infobox-info { + position: fixed; + bottom: 0; + border: 0; + background: rgb(105,105,105); + color: white; + border-radius: 2px; + font-weight: bold; +} + /* Comment Box Styles */ .coral-plugin-commentbox-container { display: flex; diff --git a/client/coral-plugin-infobox/InfoBox.js b/client/coral-plugin-infobox/InfoBox.js new file mode 100644 index 000000000..cae80df88 --- /dev/null +++ b/client/coral-plugin-infobox/InfoBox.js @@ -0,0 +1,11 @@ +import React from 'react'; +const packagename = 'coral-plugin-infobox'; + +const InfoBox = ({enable, content}) => +; + +export default InfoBox; diff --git a/client/coral-plugin-infobox/__tests__/infoBox.spec.js b/client/coral-plugin-infobox/__tests__/infoBox.spec.js new file mode 100644 index 000000000..b8b761489 --- /dev/null +++ b/client/coral-plugin-infobox/__tests__/infoBox.spec.js @@ -0,0 +1,26 @@ +import React from 'react'; +import {shallow} from 'enzyme'; +import {expect} from 'chai'; +import InfoBox from '../InfoBox'; + +describe('InfoBox', () => { + let comment; + let render; + beforeEach(() => { + comment = {}; + const postItem = (item) => { + comment.posted = item; + return Promise.resolve(4); + }; + render = shallow( comment.text = e.target.value} + item_id={'1'} + comments={['1', '2', '3']}/>); + }); + + it('should render the InfoBox appropriately', () => { + expect(render.contains('
{ beforeEach(() => { - const defaults = {id: 1, moderation: 'pre'}; + const defaults = {id: 1}; return Setting.update({id: '1'}, {$setOnInsert: defaults}, {upsert: true}); }); @@ -18,13 +18,21 @@ describe('Setting: model', () => { expect(settings).to.have.property('moderation').and.to.equal('pre'); }); }); + it('should have two infoBox fields defined', () => { + return Setting.getSettings().then(settings => { + expect(settings).to.have.property('infoBoxEnable').and.to.equal(false); + expect(settings).to.have.property('infoBoxContent').and.to.equal(''); + }); + }); }); describe('#updateSettings()', () => { it('should update the settings with a passed object', () => { - const mockSettings = {moderation: 'post'}; + const mockSettings = {moderation: 'post', infoBoxEnable: true, infoBoxContent: 'yeah'}; return Setting.updateSettings(mockSettings).then(updatedSettings => { expect(updatedSettings).to.have.property('moderation').and.to.equal('post'); + expect(updatedSettings).to.have.property('infoBoxEnable', true); + expect(updatedSettings).to.have.property('infoBoxContent', 'yeah'); }); }); }); From a0accfd993e3fd65e6cbe431a3c90d943bff0066 Mon Sep 17 00:00:00 2001 From: Dan Zajdband Date: Tue, 15 Nov 2016 13:26:39 -0500 Subject: [PATCH 39/86] Fixed infoBoxEnabled name --- client/coral-admin/src/containers/Configure.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/coral-admin/src/containers/Configure.js b/client/coral-admin/src/containers/Configure.js index 0dff4cf9a..416ef8f7b 100644 --- a/client/coral-admin/src/containers/Configure.js +++ b/client/coral-admin/src/containers/Configure.js @@ -37,13 +37,13 @@ class Configure extends React.Component { } updateInfoBoxEnable () { - const infoboxEnable = this.props.settings.infoBoxEnable; - this.props.dispatch(updateSettings({infoboxEnable})); + const infoBoxEnable = !this.props.settings.infoBoxEnable; + this.props.dispatch(updateSettings({infoBoxEnable})); } updateInfoBoxContent () { - const infoboxContent = this.props.settings.infoBoxContent; - this.props.dispatch(updateSettings({infoboxContent})); + const infoBoxContent = this.props.settings.infoBoxContent; + this.props.dispatch(updateSettings({infoBoxContent})); } saveSettings () { From fbf27a9d75f849f74c20e4ff080715b255cb5b8a Mon Sep 17 00:00:00 2001 From: David Jay Date: Tue, 15 Nov 2016 14:24:38 -0500 Subject: [PATCH 40/86] Resolving 500 error when deleting an action. --- client/coral-framework/store/actions/items.js | 3 ++- models/comment.js | 8 ++++---- routes/api/comments/index.js | 4 ++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/client/coral-framework/store/actions/items.js b/client/coral-framework/store/actions/items.js index 2e7dca70f..25aa5d54e 100644 --- a/client/coral-framework/store/actions/items.js +++ b/client/coral-framework/store/actions/items.js @@ -248,7 +248,8 @@ export function deleteAction (item_id, action_type, user_id, item_type) { return () => { const action = { action_type, - user_id + user_id, + item_id }; return fetch(`/api/v1/${item_type}/${item_id}/actions`, getInit('DELETE', action)) diff --git a/models/comment.js b/models/comment.js index 492c06e9c..d17e98860 100644 --- a/models/comment.js +++ b/models/comment.js @@ -194,12 +194,12 @@ CommentSchema.statics.removeById = function(id) { * @param {String} action_type the type of the action to be removed * @param {String} user_id the id of the user performing the action */ -CommentSchema.statics.removeAction = function(id, user_id, action_type) { +CommentSchema.statics.removeAction = function(item_id, user_id, action_type) { return Action.remove({ - action_type: action_type, + action_type, item_type: 'comment', - item_id: id, - user_id: user_id + item_id, + user_id }); }; diff --git a/routes/api/comments/index.js b/routes/api/comments/index.js index 4954563cb..05ad75b72 100644 --- a/routes/api/comments/index.js +++ b/routes/api/comments/index.js @@ -135,9 +135,9 @@ router.delete('/:comment_id', (req, res, next) => { router.delete('/:comment_id/actions', (req, res, next) => { Comment - .removeAction(req.params.comment_id, req.body.user_id, req.body.action_type) + .removeAction(req.params.item_id, req.body.user_id, req.body.action_type) .then(() => { - res.status(201).sent({}); + res.status(201).send({}); }) .catch(error => { next(error); From d1ea85468e1bb5128d31ecfc3366fd23512aa139 Mon Sep 17 00:00:00 2001 From: David Jay Date: Tue, 15 Nov 2016 14:46:08 -0500 Subject: [PATCH 41/86] Addressing issue when deleting actions. --- client/coral-framework/store/actions/items.js | 3 +-- routes/api/comments/index.js | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/coral-framework/store/actions/items.js b/client/coral-framework/store/actions/items.js index 25aa5d54e..2e7dca70f 100644 --- a/client/coral-framework/store/actions/items.js +++ b/client/coral-framework/store/actions/items.js @@ -248,8 +248,7 @@ export function deleteAction (item_id, action_type, user_id, item_type) { return () => { const action = { action_type, - user_id, - item_id + user_id }; return fetch(`/api/v1/${item_type}/${item_id}/actions`, getInit('DELETE', action)) diff --git a/routes/api/comments/index.js b/routes/api/comments/index.js index 05ad75b72..99957e987 100644 --- a/routes/api/comments/index.js +++ b/routes/api/comments/index.js @@ -134,8 +134,9 @@ router.delete('/:comment_id', (req, res, next) => { }); router.delete('/:comment_id/actions', (req, res, next) => { + console.log(req.params); Comment - .removeAction(req.params.item_id, req.body.user_id, req.body.action_type) + .removeAction(req.params.comment_id, req.body.user_id, req.body.action_type) .then(() => { res.status(201).send({}); }) From 721c0413321a44554f87304a4b57499a52cc6ebe Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Tue, 15 Nov 2016 13:33:02 -0700 Subject: [PATCH 42/86] Added fix for webpack building --- package.json | 24 +++++++----------------- webpack.config.dev.js | 2 +- webpack.config.js | 6 ------ 3 files changed, 8 insertions(+), 24 deletions(-) diff --git a/package.json b/package.json index a36fcbd3a..354340ae1 100644 --- a/package.json +++ b/package.json @@ -5,25 +5,20 @@ "main": "app.js", "scripts": { "start": "./bin/www", - "build": "webpack --config webpack.config.js --bail", - "build-watch": "webpack --config webpack.config.dev.js --watch", + "build": "NODE_ENV=production webpack --config webpack.config.js --bail", + "build-watch": "NODE_ENV=development webpack --config webpack.config.dev.js --watch", "lint": "eslint bin/* .", "lint-fix": "eslint . --fix", "pretest": "npm install", "test": "mocha --compilers js:babel-core/register --recursive tests", "test-watch": "mocha --compilers js:babel-core/register --recursive -w tests", - "embed-start": "npm run build && ./bin/www" + "embed-start": "NODE_ENV=development npm run build && ./bin/www" }, "config": { "pre-git": { "commit-msg": [], - "pre-commit": [ - "npm run lint", - "npm test" - ], - "pre-push": [ - "npm test" - ], + "pre-commit": ["npm run lint", "npm test"], + "pre-push": ["npm test"], "post-commit": [], "post-merge": [] } @@ -32,12 +27,7 @@ "type": "git", "url": "git+https://github.com/coralproject/talk.git" }, - "keywords": [ - "talk", - "coral", - "coralproject", - "ask" - ], + "keywords": ["talk", "coral", "coralproject", "ask"], "author": "", "license": "Apache-2.0", "bugs": { @@ -54,7 +44,6 @@ "mongoose": "^4.6.5", "morgan": "^1.7.0", "prompt": "^1.0.0", - "react-mdl-selectfield": "^0.2.0", "uuid": "^2.0.3" }, "devDependencies": { @@ -104,6 +93,7 @@ "react": "15.3.2", "react-dom": "15.3.2", "react-mdl": "^1.7.2", + "react-mdl-selectfield": "^0.2.0", "react-onclickoutside": "^5.7.1", "react-redux": "^4.4.5", "react-router": "^3.0.0", diff --git a/webpack.config.dev.js b/webpack.config.dev.js index 8e2f253e5..571c31625 100644 --- a/webpack.config.dev.js +++ b/webpack.config.dev.js @@ -85,7 +85,7 @@ module.exports = { }), new webpack.DefinePlugin({ 'process.env': { - 'NODE_ENV': `"${'development'}"`, + 'NODE_ENV': `"${process.env.NODE_ENV}"`, 'VERSION': `"${require('./package.json').version}"` } }) diff --git a/webpack.config.js b/webpack.config.js index bf602733f..46cd2961b 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -5,12 +5,6 @@ const devConfig = require('./webpack.config.dev'); devConfig.devtool = null; devConfig.plugins = devConfig.plugins.concat([ - new webpack.DefinePlugin({ - 'process.env': { - 'NODE_ENV': `"${'production'}"`, - 'VERSION': `"${require('./package.json').version}"` - } - }), new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false From 954f6d9ee37d09840dd6b16295406bd3a5cfa832 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Tue, 15 Nov 2016 17:41:41 -0300 Subject: [PATCH 43/86] Sign in with local account --- client/coral-framework/actions/auth.js | 12 ++++++------ client/coral-framework/reducers/auth.js | 13 +++++++++---- client/coral-sign-in/components/SignInContent.js | 2 +- client/coral-sign-in/components/styles.css | 12 +++++------- client/coral-sign-in/containers/SignInContainer.js | 1 + 5 files changed, 22 insertions(+), 18 deletions(-) diff --git a/client/coral-framework/actions/auth.js b/client/coral-framework/actions/auth.js index 3981d8021..896fb9aab 100644 --- a/client/coral-framework/actions/auth.js +++ b/client/coral-framework/actions/auth.js @@ -16,15 +16,15 @@ export const cleanState = () => ({type: actions.CLEAN_STATE}); // Sign In Actions const signInRequest = () => ({type: actions.FETCH_SIGNIN_REQUEST}); -const signInSuccess = () => ({type: actions.FETCH_SIGNIN_SUCCESS}); -const signInFailure = () => ({type: actions.FETCH_SIGNIN_FAILURE}); +const signInSuccess = (user) => ({type: actions.FETCH_SIGNIN_SUCCESS, user}); +const signInFailure = (error) => ({type: actions.FETCH_SIGNIN_FAILURE, error}); -export const fetchSignIn = () => dispatch => { +export const fetchSignIn = (formData) => dispatch => { dispatch(signInRequest()); - fetch(`${base}/auth`, getInit('POST')) + fetch(`${base}/auth/local`, getInit('POST', formData)) .then(handleResp) - .then(() => dispatch(signInSuccess())) - .catch(error => dispatch(signInFailure(error))); + .then(({user}) => dispatch(signInSuccess(user))) + .catch(() => dispatch(signInFailure('Email and/or password combination incorrect.'))); }; // Sign In - Facebook diff --git a/client/coral-framework/reducers/auth.js b/client/coral-framework/reducers/auth.js index 89e7c8012..001c98346 100644 --- a/client/coral-framework/reducers/auth.js +++ b/client/coral-framework/reducers/auth.js @@ -17,10 +17,10 @@ const initialState = Map({ auth: Map(), isLoading: false, loggedIn: false, - error: '', user: null, showSignInDialog: false, - view: 'SIGNIN' + view: 'SIGNIN', + signInError: '' }); export const getEmail = state => state.auth.formData.email; @@ -42,10 +42,15 @@ export default function auth (state = initialState, action) { .set('isLoading', true); case FETCH_SIGNIN_FAILURE: return state - .set('isLoading', false); + .set('isLoading', false) + .set('signInError', action.error); case FETCH_SIGNIN_SUCCESS: return state - .set('isLoading', false); + .set('signInError', '') + .set('isLoading', false) + .set('loggedIn', true) + .set('user', action.user) + .set('showSignInDialog', false); case FETCH_SIGNIN_FACEBOOK_SUCCESS: return state .set('user', action.user) diff --git a/client/coral-sign-in/components/SignInContent.js b/client/coral-sign-in/components/SignInContent.js index 6a1e726c5..aa3b712fa 100644 --- a/client/coral-sign-in/components/SignInContent.js +++ b/client/coral-sign-in/components/SignInContent.js @@ -27,7 +27,7 @@ const SignInContent = ({handleChange, formData, ...props}) => ( {lang.t('signIn.or')}
- { props.message && {props.message} } + { props.signInError && {props.signInError} }
From 399ba72e0e24ee4a353897290406db16f3abf2a9 Mon Sep 17 00:00:00 2001 From: gaba Date: Tue, 15 Nov 2016 15:15:41 -0800 Subject: [PATCH 44/86] It changes translations to json file. It adds infobox setting. --- client/coral-admin/src/components/Comment.js | 2 +- .../src/components/ModerationKeysModal.js | 2 +- .../src/containers/Community/Community.js | 2 +- .../coral-admin/src/containers/Configure.css | 2 + .../coral-admin/src/containers/Configure.js | 21 ++++++--- .../src/containers/ModerationQueue.js | 4 +- client/coral-admin/src/translations.js | 47 ------------------- client/coral-admin/src/translations.json | 47 +++++++++++++++++++ 8 files changed, 69 insertions(+), 58 deletions(-) delete mode 100644 client/coral-admin/src/translations.js create mode 100644 client/coral-admin/src/translations.json diff --git a/client/coral-admin/src/components/Comment.js b/client/coral-admin/src/components/Comment.js index 2b0b7561b..c115bcf61 100644 --- a/client/coral-admin/src/components/Comment.js +++ b/client/coral-admin/src/components/Comment.js @@ -4,7 +4,7 @@ import {Button, Icon} from 'react-mdl'; import timeago from 'timeago.js'; import styles from './CommentList.css'; import I18n from 'coral-framework/i18n/i18n'; -import translations from '../translations'; +import translations from '../translations.json'; // Render a single comment for the list export default props => ( diff --git a/client/coral-admin/src/components/ModerationKeysModal.js b/client/coral-admin/src/components/ModerationKeysModal.js index 5cf9fe17e..d350c74e0 100644 --- a/client/coral-admin/src/components/ModerationKeysModal.js +++ b/client/coral-admin/src/components/ModerationKeysModal.js @@ -1,5 +1,5 @@ import I18n from 'coral-framework/i18n/i18n'; -import translations from '../translations'; +import translations from '../translations.json'; import React from 'react'; import Modal from 'components/Modal'; import styles from './ModerationKeysModal.css'; diff --git a/client/coral-admin/src/containers/Community/Community.js b/client/coral-admin/src/containers/Community/Community.js index 8e0b955a4..1f009429b 100644 --- a/client/coral-admin/src/containers/Community/Community.js +++ b/client/coral-admin/src/containers/Community/Community.js @@ -1,6 +1,6 @@ import React from 'react'; import I18n from 'coral-framework/i18n/i18n'; -import translations from '../../translations'; +import translations from '../../translations.json'; import {Grid, Cell} from 'react-mdl'; import styles from './Community.css'; diff --git a/client/coral-admin/src/containers/Configure.css b/client/coral-admin/src/containers/Configure.css index c832e96c1..a70e68746 100644 --- a/client/coral-admin/src/containers/Configure.css +++ b/client/coral-admin/src/containers/Configure.css @@ -27,6 +27,8 @@ border: 1px solid #ccc; border-radius: 4px; margin-bottom: 10px; + cursor: pointer; + width: auto; } .configSettingEmbed { diff --git a/client/coral-admin/src/containers/Configure.js b/client/coral-admin/src/containers/Configure.js index 416ef8f7b..bafe335ab 100644 --- a/client/coral-admin/src/containers/Configure.js +++ b/client/coral-admin/src/containers/Configure.js @@ -14,7 +14,7 @@ import { } from 'react-mdl'; import styles from './Configure.css'; import I18n from 'coral-framework/i18n/i18n'; -import translations from '../translations'; +import translations from '../translations.json'; class Configure extends React.Component { constructor (props) { @@ -24,6 +24,8 @@ class Configure extends React.Component { this.copyToClipBoard = this.copyToClipBoard.bind(this); this.updateModeration = this.updateModeration.bind(this); + this.updateInfoBoxEnable = this.updateInfoBoxEnable.bind(this); + this.updateInfoBoxContent = this.updateInfoBoxContent.bind(this); this.saveSettings = this.saveSettings.bind(this); } @@ -41,8 +43,8 @@ class Configure extends React.Component { this.props.dispatch(updateSettings({infoBoxEnable})); } - updateInfoBoxContent () { - const infoBoxContent = this.props.settings.infoBoxContent; + updateInfoBoxContent (event) { + const infoBoxContent = event.target.value; this.props.dispatch(updateSettings({infoBoxContent})); } @@ -60,17 +62,24 @@ class Configure extends React.Component { Enable pre-moderation - + + Include Comment Stream Description for Readers. + Write a message to be added to the top of your comment stream. Pose a topic, include community guidelines, etc. + + + + rows={3} + style={{width: '100%'}}/> {/* diff --git a/client/coral-admin/src/containers/ModerationQueue.js b/client/coral-admin/src/containers/ModerationQueue.js index 9178bda57..cfc48ae6b 100644 --- a/client/coral-admin/src/containers/ModerationQueue.js +++ b/client/coral-admin/src/containers/ModerationQueue.js @@ -6,7 +6,7 @@ import {updateStatus} from 'actions/comments'; import styles from './ModerationQueue.css'; import key from 'keymaster'; import I18n from 'coral-framework/i18n/i18n'; -import translations from '../translations'; +import translations from '../translations.json'; /* * Renders the moderation queue as a tabbed layout with 3 moderation @@ -91,7 +91,7 @@ class ModerationQueue extends React.Component { commentIds={ comments .get('ids') - .filter(id => + .filter(id => comments .get('byId') .get(id) diff --git a/client/coral-admin/src/translations.js b/client/coral-admin/src/translations.js deleted file mode 100644 index 67a8142fd..000000000 --- a/client/coral-admin/src/translations.js +++ /dev/null @@ -1,47 +0,0 @@ -export default { - en: { - 'community': { - username_and_email: 'Username and Email', - account_creation_date: 'Account Creation Date' - }, - 'modqueue': { - 'pending': 'pending', - 'rejected': 'rejected', - 'flagged': 'flagged', - 'shortcuts': 'Shortcuts', - 'close': 'Close', - 'actions': 'Actions', - 'navigation': 'Navigation', - 'approve': 'Approve comment', - 'reject': 'Reject comment', - 'nextcomment': 'Go to the next comment', - 'prevcomment': 'Go to the previous comment', - 'singleview': 'Toggle single comment edit view', - 'thismenu': 'Open this menu' - }, - 'comment': { - 'flagged': 'flagged', - 'anon': 'Anonymous' - }, - 'embedlink': { - 'copy': 'Copy to Clipboard' - } - }, - es: { - 'community': { - username_and_email: 'Usuario y E-mail', - account_creation_date: 'Fecha de creación de la cuenta' - }, - 'modqueue': { - 'pending': 'pendiente', - 'rejected': 'rechazado', - 'flagged': 'marcado', - 'shortcuts': 'Atajos de teclado', - 'close': 'Cerrar' - }, - 'comment': { - 'flagged': 'marcado', - 'anon': 'Anónimo' - } - } -}; diff --git a/client/coral-admin/src/translations.json b/client/coral-admin/src/translations.json new file mode 100644 index 000000000..43d0939e0 --- /dev/null +++ b/client/coral-admin/src/translations.json @@ -0,0 +1,47 @@ +{ + "en": { + "community": { + "username_and_email": "Username and Email", + "account_creation_date": "Account Creation Date" + }, + "modqueue": { + "pending": "pending", + "rejected": "rejected", + "flagged": "flagged", + "shortcuts": "Shortcuts", + "close": "Close", + "actions": "Actions", + "navigation": "Navigation", + "approve": "Approve comment", + "reject": "Reject comment", + "nextcomment": "Go to the next comment", + "prevcomment": "Go to the previous comment", + "singleview": "Toggle single comment edit view", + "thismenu": "Open this menu" + }, + "comment": { + "flagged": "flagged", + "anon": "Anonymous" + }, + "embedlink": { + "copy": "Copy to Clipboard" + } + }, + "es": { + "community": { + "username_and_email": "Usuario y E-mail", + "account_creation_date": "Fecha de creación de la cuenta" + }, + "modqueue": { + "pending": "pendiente", + "rejected": "rechazado", + "flagged": "marcado", + "shortcuts": "Atajos de teclado", + "close": "Cerrar" + }, + "comment": { + "flagged": "marcado", + "anon": "Anónimo" + } + } +} From d24c7c0bc28dca666f46a21cba16fe6874bc3ae4 Mon Sep 17 00:00:00 2001 From: gaba Date: Tue, 15 Nov 2016 15:18:17 -0800 Subject: [PATCH 45/86] Struggling with hiding elements. --- client/coral-embed-stream/src/CommentStream.js | 4 ++-- client/coral-embed-stream/style/default.css | 6 ++++++ client/coral-plugin-infobox/InfoBox.js | 3 +-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/client/coral-embed-stream/src/CommentStream.js b/client/coral-embed-stream/src/CommentStream.js index 004f5b642..d8f5718a7 100644 --- a/client/coral-embed-stream/src/CommentStream.js +++ b/client/coral-embed-stream/src/CommentStream.js @@ -102,8 +102,8 @@ class CommentStream extends Component { ?
+ content={this.props.config.infoBoxContent} + enable={this.props.config.infoBoxEnable}/> diff --git a/client/coral-embed-stream/style/default.css b/client/coral-embed-stream/style/default.css index 08b1d8041..9875198cb 100644 --- a/client/coral-embed-stream/style/default.css +++ b/client/coral-embed-stream/style/default.css @@ -58,6 +58,12 @@ hr { color: white; border-radius: 2px; font-weight: bold; + display: block; +} + +.hidden { + visibility: hidden; + display: none; } /* Comment Box Styles */ diff --git a/client/coral-plugin-infobox/InfoBox.js b/client/coral-plugin-infobox/InfoBox.js index cae80df88..2aaa2f486 100644 --- a/client/coral-plugin-infobox/InfoBox.js +++ b/client/coral-plugin-infobox/InfoBox.js @@ -3,8 +3,7 @@ const packagename = 'coral-plugin-infobox'; const InfoBox = ({enable, content}) => ; From c86e1e8d47452a03470b0c35640b1355f1780756 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Tue, 15 Nov 2016 16:21:25 -0700 Subject: [PATCH 46/86] create and decode jwt --- models/user.js | 24 +++++++++++++++++++++++- package.json | 3 ++- routes/admin/index.js | 14 +++++++++++++- routes/api/user/index.js | 34 ++++++++++++++++++++++++++++++++++ services/mailer.js | 17 ----------------- 5 files changed, 72 insertions(+), 20 deletions(-) diff --git a/models/user.js b/models/user.js index c6197a220..f4edc8f63 100644 --- a/models/user.js +++ b/models/user.js @@ -1,6 +1,7 @@ const mongoose = require('../mongoose'); const uuid = require('uuid'); const bcrypt = require('bcrypt'); +const jwt = require('jsonwebtoken'); const SALT_ROUNDS = 10; @@ -83,10 +84,15 @@ UserSchema.options.toObject.transform = (doc, ret, options) => { * @param {Function} done [description] */ UserSchema.statics.findLocalUser = function(email, password) { + + if (!email || typeof email !== 'string') { + return Promise.reject('email is required for findLocalUser'); + } + return User.findOne({ profiles: { $elemMatch: { - id: email, + id: email.toLowerCase(), provider: 'local' } } @@ -221,6 +227,8 @@ UserSchema.statics.createLocalUser = function(email, password, displayName) { return Promise.reject('email is required'); } + email = email.toLowerCase(); + if (!password) { return Promise.reject('password is required'); } @@ -338,6 +346,20 @@ UserSchema.statics.findByIdArray = function(ids) { }); }; +UserSchema.statics.createJWT = function (email) { + if (!email || typeof email !== 'string') { + return Promise.reject('email is required when creating a JWT for resetting passord'); + } + + email = email.toLowerCase(); + + const payload = {email, jti: uuid.v4()}; + + const token = jwt.sign(payload, process.env.TALK_SESSION_SECRET, {expiresIn: '1d'}); + + return Promise.resolve(token); +}; + const User = mongoose.model('User', UserSchema); module.exports = User; diff --git a/package.json b/package.json index ad0e2dbb0..76173b7aa 100644 --- a/package.json +++ b/package.json @@ -51,13 +51,13 @@ "debug": "^2.2.0", "ejs": "^2.5.2", "express": "^4.14.0", + "jsonwebtoken": "^7.1.9", "lodash": "^4.16.6", "mongoose": "^4.6.5", "morgan": "^1.7.0", "nodemailer": "^2.6.4", "nodemailer-sendgrid-transport": "^0.2.0", "prompt": "^1.0.0", - "react-mdl-selectfield": "^0.2.0", "uuid": "^2.0.3" }, "devDependencies": { @@ -107,6 +107,7 @@ "react": "15.3.2", "react-dom": "15.3.2", "react-mdl": "^1.7.2", + "react-mdl-selectfield": "^0.2.0", "react-onclickoutside": "^5.7.1", "react-redux": "^4.4.5", "react-router": "^3.0.0", diff --git a/routes/admin/index.js b/routes/admin/index.js index 52957f54a..84c130ba8 100644 --- a/routes/admin/index.js +++ b/routes/admin/index.js @@ -1,11 +1,23 @@ const express = require('express'); - +const jwt = require('jsonwebtoken'); const router = express.Router(); router.get('/embed/stream/preview', (req, res) => { res.render('embed-stream', {basePath: '/client/embed/stream'}); }); +router.get('/password-reset/:token', (req, res, next) => { + jwt.verify(req.params.token, process.env.TALK_SESSION_SECRET, (error, decoded) => { + if (error) { + return res.status(400).json({error}); + } + + console.log(decoded); + + res.json(decoded); + }); +}); + router.get('*', (req, res) => { res.render('admin', {basePath: '/client/coral-admin'}); }); diff --git a/routes/api/user/index.js b/routes/api/user/index.js index 4665f4e52..c0bc43c6a 100644 --- a/routes/api/user/index.js +++ b/routes/api/user/index.js @@ -1,6 +1,7 @@ const express = require('express'); const router = express.Router(); const User = require('../../../models/user'); +const mailer = require('../../../services/mailer'); router.get('/', (req, res, next) => { const { @@ -70,4 +71,37 @@ router.post('/:user_id/role', (req, res, next) => { .catch(next); }); +/** + * this endpoint takes an email (username) and checks if it belongs to a User account + * if it does, create a JWT and send an email + */ +router.post('/request-password-reset', (req, res, next) => { + const {email} = req.body; + + console.log('/request-password-reset', req.body); + + if (!email) { + return next(); + } + + User + .createJWT(email) + .then(token => { + const options = { + subject: 'password reset requested', + from: 'coralcore@mozillafoundation.org', + to: 'riley.davis@gmail.com', + html: `reset password` + }; + return mailer.sendSimple(options); + }) + .then(success => { + console.log(success); + res.json({success: true}); + }) + .catch(error => { + res.status(500).json({error}); + }); +}); + module.exports = router; diff --git a/services/mailer.js b/services/mailer.js index ec1dd5f5e..eb900d782 100644 --- a/services/mailer.js +++ b/services/mailer.js @@ -8,23 +8,6 @@ const options = { const transporter = nodemailer.createTransport(sgTransport(options)); -transporter.sendMail({ - from: 'support@mrdavis.com', - to: 'riley.davis@gmail.com', - subject: 'this is only a test', - text: 'this is the body of the email maybe?', - html: `
- - - - - - - - -
foobar
rileydavis
` -}); - const mailer = { /** * sendSimple From 3abc0692bbd85995e71aa5ec314e23f80e67de44 Mon Sep 17 00:00:00 2001 From: gaba Date: Tue, 15 Nov 2016 15:31:12 -0800 Subject: [PATCH 47/86] At the top, not the bottom. --- client/coral-embed-stream/style/default.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/coral-embed-stream/style/default.css b/client/coral-embed-stream/style/default.css index 793ab0f8c..2f7394d4d 100644 --- a/client/coral-embed-stream/style/default.css +++ b/client/coral-embed-stream/style/default.css @@ -52,7 +52,7 @@ hr { /* Info Box Styles */ .coral-plugin-infobox-info { position: fixed; - bottom: 0; + top: 0; border: 0; background: rgb(105,105,105); color: white; From 621550e261e8676f278088b9562e11897abd326e Mon Sep 17 00:00:00 2001 From: gaba Date: Tue, 15 Nov 2016 15:31:54 -0800 Subject: [PATCH 48/86] Null, not empty string. --- client/coral-plugin-infobox/InfoBox.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/coral-plugin-infobox/InfoBox.js b/client/coral-plugin-infobox/InfoBox.js index 2aaa2f486..db2ea22c4 100644 --- a/client/coral-plugin-infobox/InfoBox.js +++ b/client/coral-plugin-infobox/InfoBox.js @@ -3,7 +3,7 @@ const packagename = 'coral-plugin-infobox'; const InfoBox = ({enable, content}) =>
+ className={`${packagename}-info ${enable ? null : ', hidden'}` }> {content}
; From ff2c3cf30b6fa2cc822194e582912f8596ee7bcb Mon Sep 17 00:00:00 2001 From: gaba Date: Tue, 15 Nov 2016 20:31:24 -0800 Subject: [PATCH 49/86] Hide the textfield when not enable. --- client/coral-admin/src/containers/Configure.css | 4 ++++ client/coral-admin/src/containers/Configure.js | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/client/coral-admin/src/containers/Configure.css b/client/coral-admin/src/containers/Configure.css index a70e68746..7141059b1 100644 --- a/client/coral-admin/src/containers/Configure.css +++ b/client/coral-admin/src/containers/Configure.css @@ -61,3 +61,7 @@ font-size: 14px; letter-spacing: 0.03em; } + +.hidden { + display: none; +} diff --git a/client/coral-admin/src/containers/Configure.js b/client/coral-admin/src/containers/Configure.js index bafe335ab..7d33e8e5a 100644 --- a/client/coral-admin/src/containers/Configure.js +++ b/client/coral-admin/src/containers/Configure.js @@ -72,7 +72,7 @@ class Configure extends React.Component { Write a message to be added to the top of your comment stream. Pose a topic, include community guidelines, etc. - + Date: Wed, 16 Nov 2016 03:07:27 -0800 Subject: [PATCH 50/86] It adds translations for the admin and fix CSS. --- client/coral-admin/src/components/Header.js | 12 +++-- .../coral-admin/src/components/ui/Drawer.js | 10 ++-- .../coral-admin/src/components/ui/Header.js | 10 ++-- .../coral-admin/src/containers/Configure.css | 7 +++ .../coral-admin/src/containers/Configure.js | 49 +++++++------------ client/coral-admin/src/translations.json | 26 ++++++++++ 6 files changed, 74 insertions(+), 40 deletions(-) diff --git a/client/coral-admin/src/components/Header.js b/client/coral-admin/src/components/Header.js index 1a89da513..b2bee0a44 100644 --- a/client/coral-admin/src/components/Header.js +++ b/client/coral-admin/src/components/Header.js @@ -2,22 +2,26 @@ import React from 'react'; import {Layout, Navigation, Drawer, Header} from 'react-mdl'; import {Link} from 'react-router'; import styles from './Header.css'; +import I18n from 'coral-framework/i18n/i18n'; +import translations from '../translations.json'; // App header. If we add a navbar it should be here export default (props) => (
- Moderate - Configure + {lang.t('configure.moderate')} + {lang.t('Configure')}
- Moderate - Configure + {lang.t('configure.moderate')} + {lang.t('configure.Configure')} {props.children}
); + +const lang = new I18n(translations); diff --git a/client/coral-admin/src/components/ui/Drawer.js b/client/coral-admin/src/components/ui/Drawer.js index 9ea727911..12bf7c1d2 100644 --- a/client/coral-admin/src/components/ui/Drawer.js +++ b/client/coral-admin/src/components/ui/Drawer.js @@ -2,13 +2,17 @@ import React from 'react'; import {Navigation, Drawer} from 'react-mdl'; import {Link} from 'react-router'; import styles from './Header.css'; +import I18n from 'coral-framework/i18n/i18n'; +import translations from '../../translations.json'; export default () => ( - Moderate - Community - Configure + {lang.t('configure.moderate')} + {lang.t('configure.community')} + {lang.t('configure.configure')} ); + +const lang = new I18n(translations); diff --git a/client/coral-admin/src/components/ui/Header.js b/client/coral-admin/src/components/ui/Header.js index 86e622672..d85a91744 100644 --- a/client/coral-admin/src/components/ui/Header.js +++ b/client/coral-admin/src/components/ui/Header.js @@ -2,16 +2,20 @@ import React from 'react'; import {Navigation, Header} from 'react-mdl'; import {Link} from 'react-router'; import styles from './Header.css'; +import I18n from 'coral-framework/i18n/i18n'; +import translations from '../../translations.json'; export default () => (
- Moderate - Community - Configure + {lang.t('configure.moderate')} + {lang.t('configure.community')} + {lang.t('configure.configure')} {`v${process.env.VERSION}`}
); + +const lang = new I18n(translations); diff --git a/client/coral-admin/src/containers/Configure.css b/client/coral-admin/src/containers/Configure.css index 7141059b1..8d1fc49a2 100644 --- a/client/coral-admin/src/containers/Configure.css +++ b/client/coral-admin/src/containers/Configure.css @@ -29,6 +29,13 @@ margin-bottom: 10px; cursor: pointer; width: auto; + height: auto; + text-align: left; +} + +.configSettingInfoBox p { + font-size: 12px; + bottom: 0; } .configSettingEmbed { diff --git a/client/coral-admin/src/containers/Configure.js b/client/coral-admin/src/containers/Configure.js index 7d33e8e5a..7d057eacc 100644 --- a/client/coral-admin/src/containers/Configure.js +++ b/client/coral-admin/src/containers/Configure.js @@ -60,7 +60,7 @@ class Configure extends React.Component { onClick={this.updateModeration} checked={this.props.settings.moderation === 'pre'} /> - Enable pre-moderation + {lang.t('configure.enable-pre-moderation')}
@@ -68,33 +68,22 @@ class Configure extends React.Component { onClick={this.updateInfoBoxEnable} checked={this.props.settings.infoBoxEnable} /> - Include Comment Stream Description for Readers. - Write a message to be added to the top of your comment stream. Pose a topic, include community guidelines, etc. + + {lang.t('configure.include-comment-stream')} +

+ {lang.t('configure.include-comment-stream-desc')} +

- + + + - {/* - - - Include Comment Stream Description for Readers - - - - Limit Comment Length - - - */} ; } @@ -115,7 +104,7 @@ class Configure extends React.Component { return -

Copy and paste code below into your CMS to embed your comment box in your articles

+

{lang.t('configure.copy-and-paste')}