diff --git a/analytics/package.json b/analytics/package.json index 87cc4c648b..ee5dfb4b32 100644 --- a/analytics/package.json +++ b/analytics/package.json @@ -6,7 +6,7 @@ }, "dependencies": { "amplitude": "^3.5.0", - "aws-sdk": "^2.340.0", + "aws-sdk": "^2.354.0", "bull": "3.3.10", "faker": "^4.1.0", "lodash.intersection": "^4.4.0", diff --git a/analytics/yarn.lock b/analytics/yarn.lock index d4fc6ac70a..39e16d2155 100644 --- a/analytics/yarn.lock +++ b/analytics/yarn.lock @@ -12,9 +12,9 @@ asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" -aws-sdk@^2.340.0: - version "2.340.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.340.0.tgz#17799ee456793248eae10c8c3847f59d5b6a27c7" +aws-sdk@^2.354.0: + version "2.354.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.354.0.tgz#4c5a8d7b1a77b52dfb8724136656339e2cca3f5e" dependencies: buffer "4.9.1" events "1.1.1" diff --git a/api/mutations/user/editUser.js b/api/mutations/user/editUser.js index 4f5dbdcc0b..b7b1a9db92 100644 --- a/api/mutations/user/editUser.js +++ b/api/mutations/user/editUser.js @@ -7,26 +7,25 @@ import { getUserByUsername, getUserById, editUser, + getUsersByEmail, + setUserPendingEmail, } from 'shared/db/queries/user'; import { events } from 'shared/analytics'; import { trackQueue } from 'shared/bull/queues'; import { isAuthedResolver as requireAuth } from '../../utils/permissions'; +import isEmail from 'validator/lib/isEmail'; +import { sendEmailValidationEmailQueue } from 'shared/bull/queues'; export default requireAuth( - async ( - _: any, - args: EditUserInput, - { user, updateCookieUserData }: GraphQLContext - ) => { - const currentUser = user; + async (_: any, args: EditUserInput, ctx: GraphQLContext) => { + const { user: currentUser, updateCookieUserData } = ctx; + const { input } = args; + // If the user is trying to change their username check whether there's a person with that username already - if (args.input.username) { - if ( - args.input.username === 'null' || - args.input.username === 'undefined' - ) { + if (input.username) { + if (input.username === 'null' || input.username === 'undefined') { trackQueue.add({ - userId: user.id, + userId: currentUser.id, event: events.USER_EDITED_FAILED, properties: { reason: 'bad username input', @@ -36,10 +35,10 @@ export default requireAuth( return new UserError('Nice try! 😉'); } - const dbUser = await getUserByUsername(args.input.username); - if (dbUser && dbUser.id !== user.id) { + const dbUser = await getUserByUsername(input.username); + if (dbUser && dbUser.id !== currentUser.id) { trackQueue.add({ - userId: user.id, + userId: currentUser.id, event: events.USER_EDITED_FAILED, properties: { reason: 'username taken', @@ -52,7 +51,32 @@ export default requireAuth( } } - const editedUser = await editUser(args, user.id); + if (input.email && typeof input.email === 'string') { + const pendingEmail = input.email; + + // if user is changing their email, make sure it's not taken by someone else + if (pendingEmail !== currentUser.email) { + if (!isEmail(input.email)) { + return new UserError('Please enter a valid email address.'); + } + + const dbUsers = await getUsersByEmail(pendingEmail); + if (dbUsers && dbUsers.length > 0) { + return new UserError('Please enter a valid email address.'); + } + + // the user will have to confirm their email for it to be saved in + // order to prevent spoofing your email as someone elses + await setUserPendingEmail(currentUser.id, pendingEmail).then(() => { + sendEmailValidationEmailQueue.add({ + email: pendingEmail, + userId: currentUser.id, + }); + }); + } + } + + const editedUser = await editUser(args, currentUser.id); await updateCookieUserData({ ...editedUser, diff --git a/api/package.json b/api/package.json index 8a651f6029..06adaa1e6c 100644 --- a/api/package.json +++ b/api/package.json @@ -6,7 +6,7 @@ "algoliasearch": "^3.30.0", "apollo-engine": "^1.1.2", "apollo-local-query": "^0.3.0", - "apollo-server-express": "^2.2.0", + "apollo-server-express": "^2.2.2", "apollo-upload-client": "^8.1.0", "aws-sdk": "2.200.0", "axios": "^0.16.2", @@ -115,7 +115,7 @@ "serialize-javascript": "^1.5.0", "session-rethinkdb": "^2.0.0", "shortid": "^2.2.14", - "slate": "^0.44.4", + "slate": "^0.44.6", "slate-markdown": "0.1.0", "slugg": "^1.1.0", "string-replace-to-array": "^1.0.3", diff --git a/api/types/User.js b/api/types/User.js index c18909388b..47b5e89a15 100644 --- a/api/types/User.js +++ b/api/types/User.js @@ -142,6 +142,7 @@ const User = /* GraphQL */ ` website: String username: LowercaseString timezone: Int + email: String } input UpgradeToProInput { diff --git a/api/yarn.lock b/api/yarn.lock index 2cb8472d2c..22880f1fa2 100644 --- a/api/yarn.lock +++ b/api/yarn.lock @@ -2,6 +2,12 @@ # yarn lockfile v1 +"@apollographql/apollo-tools@^0.2.6": + version "0.2.6" + resolved "https://registry.yarnpkg.com/@apollographql/apollo-tools/-/apollo-tools-0.2.6.tgz#a3eedd6baf84e27a92b0fb660b42ce1795d6ee92" + dependencies: + apollo-env "0.2.3" + "@apollographql/apollo-upload-server@^5.0.3": version "5.0.3" resolved "https://registry.yarnpkg.com/@apollographql/apollo-upload-server/-/apollo-upload-server-5.0.3.tgz#8558c378ff6457de82147e5072c96a6b242773b7" @@ -1100,12 +1106,12 @@ anymatch@^2.0.0: micromatch "^3.1.4" normalize-path "^2.1.1" -apollo-cache-control@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/apollo-cache-control/-/apollo-cache-control-0.3.0.tgz#e01e7b902d097c49b15642664ded9a96713a8578" +apollo-cache-control@0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/apollo-cache-control/-/apollo-cache-control-0.3.2.tgz#fc98781e6df60346a20b0540450d02842c1212f2" dependencies: apollo-server-env "2.2.0" - graphql-extensions "0.3.0" + graphql-extensions "0.3.2" apollo-datasource@0.2.0: version "0.2.0" @@ -1132,14 +1138,14 @@ apollo-engine-reporting-protobuf@0.1.0: dependencies: protobufjs "^6.8.6" -apollo-engine-reporting@0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/apollo-engine-reporting/-/apollo-engine-reporting-0.1.0.tgz#a6ce49eec0f816e78f1d72f08c2297c3d2338098" +apollo-engine-reporting@0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/apollo-engine-reporting/-/apollo-engine-reporting-0.1.2.tgz#6247e8d618da5c084ccb1bc76c665ce7a1b1ba55" dependencies: apollo-engine-reporting-protobuf "0.1.0" apollo-server-env "2.2.0" async-retry "^1.2.1" - graphql-extensions "0.3.0" + graphql-extensions "0.3.2" lodash "^4.17.10" apollo-engine@^1.1.2: @@ -1157,6 +1163,13 @@ apollo-engine@^1.1.2: apollo-engine-binary-linux "0.2018.6-20-gc0e4bb519" apollo-engine-binary-windows "0.2018.6-20-gc0e4bb519" +apollo-env@0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/apollo-env/-/apollo-env-0.2.3.tgz#a03849c5b5f493f079bb42dac8a9e2a9a6286c6e" + dependencies: + core-js "^3.0.0-beta.3" + node-fetch "^2.2.0" + apollo-link-http-common@^0.2.4: version "0.2.4" resolved "https://registry.yarnpkg.com/apollo-link-http-common/-/apollo-link-http-common-0.2.4.tgz#877603f7904dc8f70242cac61808b1f8d034b2c3" @@ -1198,22 +1211,23 @@ apollo-server-caching@0.2.0: dependencies: lru-cache "^4.1.3" -apollo-server-core@2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-2.2.0.tgz#3c881abde1e934be719716a85ab6e4972f7fa463" +apollo-server-core@2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-2.2.2.tgz#66ca6b4af6c7cdd8155de946d0c4fea3357b0432" dependencies: + "@apollographql/apollo-tools" "^0.2.6" "@apollographql/apollo-upload-server" "^5.0.3" "@apollographql/graphql-playground-html" "^1.6.4" "@types/ws" "^6.0.0" - apollo-cache-control "0.3.0" + apollo-cache-control "0.3.2" apollo-datasource "0.2.0" - apollo-engine-reporting "0.1.0" + apollo-engine-reporting "0.1.2" apollo-server-caching "0.2.0" apollo-server-env "2.2.0" apollo-server-errors "2.2.0" - apollo-server-plugin-base "0.1.0" - apollo-tracing "0.3.0" - graphql-extensions "0.3.0" + apollo-server-plugin-base "0.1.2" + apollo-tracing "0.3.2" + graphql-extensions "0.3.2" graphql-subscriptions "^1.0.0" graphql-tag "^2.9.2" graphql-tools "^4.0.0" @@ -1233,9 +1247,9 @@ apollo-server-errors@2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/apollo-server-errors/-/apollo-server-errors-2.2.0.tgz#5b452a1d6ff76440eb0f127511dc58031a8f3cb5" -apollo-server-express@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/apollo-server-express/-/apollo-server-express-2.2.0.tgz#52ebe344c463832dd3e02fd4ea5f0ad3edf59058" +apollo-server-express@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/apollo-server-express/-/apollo-server-express-2.2.2.tgz#e6811024cac695351600c18985331b43ba24f556" dependencies: "@apollographql/apollo-upload-server" "^5.0.3" "@apollographql/graphql-playground-html" "^1.6.4" @@ -1244,23 +1258,23 @@ apollo-server-express@^2.2.0: "@types/cors" "^2.8.4" "@types/express" "4.16.0" accepts "^1.3.5" - apollo-server-core "2.2.0" + apollo-server-core "2.2.2" body-parser "^1.18.3" cors "^2.8.4" graphql-subscriptions "^1.0.0" graphql-tools "^4.0.0" type-is "^1.6.16" -apollo-server-plugin-base@0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/apollo-server-plugin-base/-/apollo-server-plugin-base-0.1.0.tgz#4ecf096b1f385c75c7d552b6528946181b757728" +apollo-server-plugin-base@0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/apollo-server-plugin-base/-/apollo-server-plugin-base-0.1.2.tgz#4c1ebb769b630a16ff8ade03f12759cde17a21dc" -apollo-tracing@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/apollo-tracing/-/apollo-tracing-0.3.0.tgz#20804e6c16d93c5ef1e14f123406da1f9b4dcce0" +apollo-tracing@0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/apollo-tracing/-/apollo-tracing-0.3.2.tgz#1a6b695813791b8404b5adaa10925a7f2642f15d" dependencies: apollo-server-env "2.2.0" - graphql-extensions "0.3.0" + graphql-extensions "0.3.2" apollo-upload-client@^8.1.0: version "8.1.0" @@ -2866,6 +2880,10 @@ core-js@^2.5.7: version "2.5.7" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.7.tgz#f972608ff0cead68b841a16a932d0b183791814e" +core-js@^3.0.0-beta.3: + version "3.0.0-beta.3" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.0.0-beta.3.tgz#b0f22009972b8c6c04550ebf38513ca4b3cc9559" + core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -4272,9 +4290,11 @@ graphql-depth-limit@^1.1.0: dependencies: arrify "^1.0.1" -graphql-extensions@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/graphql-extensions/-/graphql-extensions-0.3.0.tgz#49e3a77986e34d0dc0af0c07fc51e48dcdec8b0c" +graphql-extensions@0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/graphql-extensions/-/graphql-extensions-0.3.2.tgz#a19dd62b62d769f4d1b9c4b4781cc353b2174998" + dependencies: + "@apollographql/apollo-tools" "^0.2.6" graphql-log@0.1.2: version "0.1.2" @@ -6305,6 +6325,10 @@ node-fetch@^2.1.2: version "2.2.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.2.0.tgz#4ee79bde909262f9775f731e3656d0db55ced5b5" +node-fetch@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.3.0.tgz#1a1d940bbfb916a1d3e0219f037e89e71f8c5fa5" + node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" @@ -7972,9 +7996,9 @@ slate-markdown@0.1.0: react "^0.14.0 || ^15.0.0" styled-components "^2.0.0" -slate@^0.44.4: - version "0.44.4" - resolved "https://registry.yarnpkg.com/slate/-/slate-0.44.4.tgz#b8b82c2d906805aaed42e86ad331963246386aa4" +slate@^0.44.6: + version "0.44.6" + resolved "https://registry.yarnpkg.com/slate/-/slate-0.44.6.tgz#47ab3127641bc1faa3e5a5b01bfab8861843c82e" dependencies: debug "^3.1.0" direction "^0.1.5" diff --git a/athena/package.json b/athena/package.json index c5fd37773b..ee5f79019a 100644 --- a/athena/package.json +++ b/athena/package.json @@ -5,7 +5,7 @@ "start": "NODE_ENV=production node main.js" }, "dependencies": { - "aws-sdk": "^2.340.0", + "aws-sdk": "^2.354.0", "axios": "^0.16.2", "bull": "3.3.10", "cryptr": "^3.0.0", diff --git a/athena/yarn.lock b/athena/yarn.lock index 2cb8fa4667..31ce51c75d 100644 --- a/athena/yarn.lock +++ b/athena/yarn.lock @@ -20,9 +20,9 @@ asn1.js@^5.0.0: inherits "^2.0.1" minimalistic-assert "^1.0.0" -aws-sdk@^2.340.0: - version "2.340.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.340.0.tgz#17799ee456793248eae10c8c3847f59d5b6a27c7" +aws-sdk@^2.354.0: + version "2.354.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.354.0.tgz#4c5a8d7b1a77b52dfb8724136656339e2cca3f5e" dependencies: buffer "4.9.1" events "1.1.1" diff --git a/chronos/package.json b/chronos/package.json index 7302ceec80..9c67fcf6db 100644 --- a/chronos/package.json +++ b/chronos/package.json @@ -3,7 +3,7 @@ "start": "NODE_ENV=production node main.js" }, "dependencies": { - "aws-sdk": "^2.340.0", + "aws-sdk": "^2.354.0", "bull": "3.3.10", "debug": "^2.6.9", "draft-js": "^0.10.5", diff --git a/chronos/yarn.lock b/chronos/yarn.lock index 5e622694f3..ba6f9b580c 100644 --- a/chronos/yarn.lock +++ b/chronos/yarn.lock @@ -6,9 +6,9 @@ asap@~2.0.3: version "2.0.6" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" -aws-sdk@^2.340.0: - version "2.340.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.340.0.tgz#17799ee456793248eae10c8c3847f59d5b6a27c7" +aws-sdk@^2.354.0: + version "2.354.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.354.0.tgz#4c5a8d7b1a77b52dfb8724136656339e2cca3f5e" dependencies: buffer "4.9.1" events "1.1.1" diff --git a/cypress/integration/user/edit_user_spec.js b/cypress/integration/user/edit_user_spec.js index 43ecb31c41..0dcbcfbd12 100644 --- a/cypress/integration/user/edit_user_spec.js +++ b/cypress/integration/user/edit_user_spec.js @@ -34,6 +34,8 @@ describe('edit a user', () => { .clear() .type(NEW_DESCRIPTION); + cy.get('[data-cy="user-email-input"]').should('be.visible'); + cy.get('[data-cy="user-website-input"]') .should('be.visible') .click() diff --git a/desktop/package.json b/desktop/package.json index 8f5a6f66b8..f92d67fbf0 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -18,7 +18,7 @@ "electron-window-state": "^5.0.2" }, "devDependencies": { - "electron": "^3.0.8", + "electron": "^3.0.9", "electron-builder": "^20.34.0", "nodemon": "^1.18.6", "rimraf": "^2.6.2" diff --git a/desktop/yarn.lock b/desktop/yarn.lock index 45e89a8141..131ff3ebc1 100644 --- a/desktop/yarn.lock +++ b/desktop/yarn.lock @@ -763,9 +763,9 @@ electron-window-state@^5.0.2: jsonfile "^4.0.0" mkdirp "^0.5.1" -electron@^3.0.8: - version "3.0.8" - resolved "https://registry.yarnpkg.com/electron/-/electron-3.0.8.tgz#7905806ebaead4c693531e11cda6568c32efa7bb" +electron@^3.0.9: + version "3.0.9" + resolved "https://registry.yarnpkg.com/electron/-/electron-3.0.9.tgz#79bd25dfd5496918a00d579e702fb83082f1a036" dependencies: "@types/node" "^8.0.24" electron-download "^4.1.0" diff --git a/hermes/package.json b/hermes/package.json index 58ef95a8c5..15fa814361 100644 --- a/hermes/package.json +++ b/hermes/package.json @@ -3,7 +3,7 @@ "start": "NODE_ENV=production node main.js" }, "dependencies": { - "aws-sdk": "^2.340.0", + "aws-sdk": "^2.354.0", "bull": "3.3.10", "debug": "^2.6.9", "draft-js": "^0.10.5", diff --git a/hermes/yarn.lock b/hermes/yarn.lock index 93e9987f2c..6a09db64cb 100644 --- a/hermes/yarn.lock +++ b/hermes/yarn.lock @@ -6,9 +6,9 @@ asap@~2.0.3: version "2.0.6" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" -aws-sdk@^2.340.0: - version "2.340.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.340.0.tgz#17799ee456793248eae10c8c3847f59d5b6a27c7" +aws-sdk@^2.354.0: + version "2.354.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.354.0.tgz#4c5a8d7b1a77b52dfb8724136656339e2cca3f5e" dependencies: buffer "4.9.1" events "1.1.1" diff --git a/mobile/package.json b/mobile/package.json index 0f8f2ef024..fc86e3310a 100644 --- a/mobile/package.json +++ b/mobile/package.json @@ -40,7 +40,7 @@ "exp": "^52.0.0", "jest": "^22.4.4", "jest-expo": "^28.0.0", - "react-test-renderer": "^16.6.1" + "react-test-renderer": "^16.6.3" }, "scripts": { "dev": "exp start .", diff --git a/mobile/yarn.lock b/mobile/yarn.lock index de18fe36dd..f2d075c92e 100644 --- a/mobile/yarn.lock +++ b/mobile/yarn.lock @@ -6452,9 +6452,9 @@ react-is@^16.4.1: version "16.4.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.4.1.tgz#d624c4650d2c65dbd52c72622bbf389435d9776e" -react-is@^16.6.1: - version "16.6.1" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.6.1.tgz#f77b1c3d901be300abe8d58645b7a59e794e5982" +react-is@^16.6.3: + version "16.6.3" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.6.3.tgz#d2d7462fcfcbe6ec0da56ad69047e47e56e7eac0" react-lifecycles-compat@^3, react-lifecycles-compat@^3.0.4: version "3.0.4" @@ -6717,14 +6717,14 @@ react-test-renderer@^16.3.1: prop-types "^15.6.0" react-is "^16.4.1" -react-test-renderer@^16.6.1: - version "16.6.1" - resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.6.1.tgz#8ea357652be3cf81cbd6b2f686e74ebe67c17b78" +react-test-renderer@^16.6.3: + version "16.6.3" + resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.6.3.tgz#5f3a1a7d5c3379d46f7052b848b4b72e47c89f38" dependencies: object-assign "^4.1.1" prop-types "^15.6.2" - react-is "^16.6.1" - scheduler "^0.11.0" + react-is "^16.6.3" + scheduler "^0.11.2" react-timer-mixin@^0.13.2: version "0.13.3" @@ -7167,9 +7167,9 @@ sax@~1.1.1: version "1.1.6" resolved "https://registry.yarnpkg.com/sax/-/sax-1.1.6.tgz#5d616be8a5e607d54e114afae55b7eaf2fcc3240" -scheduler@^0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.11.0.tgz#def1f1bfa6550cc57981a87106e65e8aea41a6b5" +scheduler@^0.11.2: + version "0.11.2" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.11.2.tgz#a8db5399d06eba5abac51b705b7151d2319d33d3" dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" diff --git a/package.json b/package.json index 6072207673..13bbabf285 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "Spectrum", - "version": "2.4.67", + "version": "2.4.68", "license": "BSD-3-Clause", "devDependencies": { "@babel/preset-flow": "^7.0.0", diff --git a/shared/db/queries/user.js b/shared/db/queries/user.js index 303757824f..ea4db06bb2 100644 --- a/shared/db/queries/user.js +++ b/shared/db/queries/user.js @@ -84,6 +84,12 @@ export const getUserByEmail = createReadQuery((email: string) => ({ tags: (user: ?DBUser) => (user ? [user.id] : []), })); +export const getUsersByEmail = createReadQuery((email: string) => ({ + query: db.table('users').getAll(email, { index: 'email' }), + process: (users: Array) => users, + tags: (users: Array) => (users ? users.map(u => u && u.id) : []), +})); + export const getUserByUsername = createReadQuery((username: string) => ({ query: db.table('users').getAll(username, { index: 'username' }), process: (users: ?Array) => (users && users[0]) || null, diff --git a/src/views/userSettings/components/editForm.js b/src/views/userSettings/components/editForm.js index 5285c1b686..81af751e72 100644 --- a/src/views/userSettings/components/editForm.js +++ b/src/views/userSettings/components/editForm.js @@ -14,6 +14,7 @@ import { Input, TextArea, Error, + Success, PhotoInput, CoverInput, } from 'src/components/formElements'; @@ -36,6 +37,8 @@ import { import { Notice } from 'src/components/listItems/style'; import { SectionCard, SectionTitle } from 'src/components/settingsViews/style'; import type { Dispatch } from 'redux'; +import type { GetCurrentUserSettingsType } from 'shared/graphql/queries/user/getCurrentUserSettings'; +import isEmail from 'validator/lib/isEmail'; type State = { website: ?string, @@ -52,6 +55,9 @@ type State = { isLoading: boolean, photoSizeError: string, usernameError: string, + email: string, + emailError: string, + didChangeEmail: boolean, }; type Props = { @@ -59,13 +65,14 @@ type Props = { dispatch: Dispatch, client: Object, editUser: Function, + user: GetCurrentUserSettingsType, }; class UserWithData extends React.Component { constructor(props) { super(props); - const user = this.props.currentUser; + const user = this.props.user; this.state = { website: user.website ? user.website : '', @@ -82,6 +89,9 @@ class UserWithData extends React.Component { isLoading: false, photoSizeError: '', usernameError: '', + email: user.email ? user.email : '', + emailError: '', + didChangeEmail: false, }; } @@ -101,6 +111,24 @@ class UserWithData extends React.Component { }); }; + changeEmail = e => { + const email = e.target.value; + + if (!email || email.length === 0) { + return this.setState({ + email, + emailError: 'Your email can’t be blank', + didChangeEmail: false, + }); + } + + this.setState({ + email, + emailError: '', + didChangeEmail: false, + }); + }; + changeDescription = e => { const description = e.target.value; if (description.length >= 140) { @@ -199,8 +227,12 @@ class UserWithData extends React.Component { photoSizeError, username, usernameError, + email, + emailError, } = this.state; + const { user } = this.props; + const input = { name, description, @@ -208,9 +240,22 @@ class UserWithData extends React.Component { file, coverFile, username, + email, }; - if (photoSizeError || usernameError) { + if (!isEmail(email)) { + return this.setState({ + emailError: 'Please add a valid email address.', + }); + } + + if (email !== user.email) { + this.setState({ + didChangeEmail: true, + }); + } + + if (photoSizeError || usernameError || emailError) { return; } @@ -275,6 +320,9 @@ class UserWithData extends React.Component { isLoading, photoSizeError, usernameError, + email, + emailError, + didChangeEmail, } = this.state; const postAuthRedirectPath = `?r=${CLIENT_URL}/users/${username}/settings`; @@ -351,6 +399,21 @@ class UserWithData extends React.Component { Optional: Add your website + + Email + + + {didChangeEmail && ( + A confirmation email has been sent to {email}. + )} + {emailError && {emailError}} + { @@ -383,7 +446,12 @@ class UserWithData extends React.Component {