From 61aca8cb55a6991c8630fe4eac5d2508ea991433 Mon Sep 17 00:00:00 2001 From: Andrew Plummer Date: Sat, 21 Sep 2024 02:36:39 +0900 Subject: [PATCH] better phone field validation and handling --- services/api/package.json | 8 +-- services/api/src/models/definitions/user.json | 2 +- services/api/src/routes/__tests__/users.js | 18 +++++++ services/api/yarn.lock | 51 +++++++++++-------- .../web/src/components/form-fields/Phone.js | 9 +++- services/web/src/modals/EditUser.js | 1 - .../web/src/utils/phone/countries-simple.json | 2 +- services/web/src/utils/phone/index.js | 10 ++-- 8 files changed, 68 insertions(+), 33 deletions(-) diff --git a/services/api/package.json b/services/api/package.json index 7aa5752b..b302f54e 100644 --- a/services/api/package.json +++ b/services/api/package.json @@ -22,10 +22,10 @@ }, "dependencies": { "@bedrockio/config": "^2.2.3", - "@bedrockio/fixtures": "^1.1.0", + "@bedrockio/fixtures": "^1.2.3", "@bedrockio/logger": "^1.0.8", - "@bedrockio/model": "^0.3.2", - "@bedrockio/yada": "^1.1.1", + "@bedrockio/model": "^0.7.3", + "@bedrockio/yada": "^1.2.3", "@google-cloud/storage": "^7.11.1", "@koa/cors": "^4.0.0", "@koa/router": "^12.0.0", @@ -43,7 +43,7 @@ "koa-compose": "^4.1.0", "lodash": "^4.17.21", "marked": "^10.0.0", - "mongoose": "^8.4.3", + "mongoose": "^8.6.3", "mustache": "^4.2.0", "nanoid": "3.3.5", "postmark": "^3.11.0", diff --git a/services/api/src/models/definitions/user.json b/services/api/src/models/definitions/user.json index abbb0243..82668331 100644 --- a/services/api/src/models/definitions/user.json +++ b/services/api/src/models/definitions/user.json @@ -20,7 +20,7 @@ }, "phone": { "type": "String", - "validate": "phone", + "validate": "phone:US", "unique": true, "trim": true }, diff --git a/services/api/src/routes/__tests__/users.js b/services/api/src/routes/__tests__/users.js index 2683087a..20ffe3d5 100644 --- a/services/api/src/routes/__tests__/users.js +++ b/services/api/src/routes/__tests__/users.js @@ -60,6 +60,24 @@ describe('/1/users', () => { expect(updatedUser.lastName).toBe('Name'); expect(updatedUser.name).toBe('Other Name'); }); + + it('should allow unsetting phone number', async () => { + let user = await createUser({ + phone: '+15551234567', + }); + const response = await request( + 'PATCH', + '/1/users/me', + { + phone: '', + }, + { user } + ); + expect(response.status).toBe(200); + + user = await User.findById(user.id); + expect(user.phone).toBeUndefined(); + }); }); describe('POST /', () => { diff --git a/services/api/yarn.lock b/services/api/yarn.lock index 0e2f8fb1..bf5db1da 100644 --- a/services/api/yarn.lock +++ b/services/api/yarn.lock @@ -303,10 +303,10 @@ resolved "https://registry.yarnpkg.com/@bedrockio/config/-/config-2.2.3.tgz#bac37d12e36a99ec16668c8b5d1e05dec9658fa9" integrity sha512-jfOcZIs63S0GaWQjh5vVIISr4b2vA0CWgm630N0FDB6wlW+O0Fsjox92Agx5nggVpSnNYWqzjuKaUb5RY3/ECw== -"@bedrockio/fixtures@^1.1.0": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@bedrockio/fixtures/-/fixtures-1.1.1.tgz#d6774e34a3b4778ffb77c7c2968cfc1d6b418525" - integrity sha512-dCWkwGApejxwffqdxuXfD9Gnie/rXcN2qHX+nTwyTEJHivo98NMCgzUMh0Yzha1uQbzcidI+dRKQeIZm/sqHvA== +"@bedrockio/fixtures@^1.2.3": + version "1.2.3" + resolved "https://registry.yarnpkg.com/@bedrockio/fixtures/-/fixtures-1.2.3.tgz#c9fd1a75a59f9b9ecec6bc847f73f77314246121" + integrity sha512-bgch7q3KdUdY5ppz4T48R6tODohPAHdZy7TvfiCVU+QZoSmNRhbXqkeMwc9jSW/Q2sqYjyB7T0+vEz8idtgRTA== dependencies: "@bedrockio/config" "^2.2.2" "@bedrockio/logger" "^1.0.3" @@ -334,10 +334,10 @@ kleur "^4.1.5" stdout-stream "^2.0.0" -"@bedrockio/model@^0.3.2": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@bedrockio/model/-/model-0.3.2.tgz#0345703f5da7e84964ba11c3bdad198b62f437c6" - integrity sha512-C2OunXtm7t9PP+HFOcgBb8EAoCFfcNgCCPATbLlXbNa1uDsV+mg5sWz8/o/grjQBIiFXWOER7CTHlA9ErqNfVQ== +"@bedrockio/model@^0.7.3": + version "0.7.3" + resolved "https://registry.yarnpkg.com/@bedrockio/model/-/model-0.7.3.tgz#ebbf7d2288018a4a2d6a1ac7d7d1e1fad98d92f4" + integrity sha512-ET2myGL56YU0l27kqWK1nW2HSq2QsSgARRGGiKTVGABjBvT5ByCGAJKNygBSkdeg7rxOyxYlTUbvf2sxhSoRZQ== dependencies: "@bedrockio/logger" "^1.0.6" lodash "^4.17.21" @@ -347,10 +347,10 @@ resolved "https://registry.yarnpkg.com/@bedrockio/prettier-config/-/prettier-config-1.0.2.tgz#89878381d3059b810f97514ee6b69f946a6d1654" integrity sha512-Ebcx/D9FcV6WEQH2gXA4SkN62fus/aVMW46MYFm1/y+S0GVYj+OCuBbRMM4F8YAq9ntJ+B/z9fLiOheNpEpRzA== -"@bedrockio/yada@^1.1.1": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@bedrockio/yada/-/yada-1.1.1.tgz#fea824ba0658bf1ee79215c487364184bf463f55" - integrity sha512-IByiuIU3sLvqs/e1NvDVKYBlbpctB19vhfcieQaibcu5m0Y899WtpDm5Iw24Fnr13YHm4gar/4WFPGYc64Mlnw== +"@bedrockio/yada@^1.2.3": + version "1.2.3" + resolved "https://registry.yarnpkg.com/@bedrockio/yada/-/yada-1.2.3.tgz#6b6d650b2a348f597e7d3ccc61f736371e7725b5" + integrity sha512-yTGICYsurRpyiz2GznXOcUcKIfrqU0Hyf1ADXLhO3D2oeuo3V7pS72TgzoVr5KOzTlpGwTs6s5nWkkdUiRGW3w== dependencies: validator "^13.9.0" @@ -4841,10 +4841,10 @@ mongodb-memory-server@9.2.0: mongodb-memory-server-core "9.2.0" tslib "^2.6.2" -mongodb@6.6.2, mongodb@^6.5.0: - version "6.6.2" - resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-6.6.2.tgz#7ecdd788e9162f6c5726cef40bdd2813cc01e56c" - integrity sha512-ZF9Ugo2JCG/GfR7DEb4ypfyJJyiKbg5qBYKRintebj8+DNS33CyGMkWbrS9lara+u+h+yEOGSRiLhFO/g1s1aw== +mongodb@6.8.0: + version "6.8.0" + resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-6.8.0.tgz#680450f113cdea6d2d9f7121fe57cd29111fd2ce" + integrity sha512-HGQ9NWDle5WvwMnrvUxsFYPd3JEbqD3RgABHBQRuoCEND0qzhsd0iH5ypHsf1eJ+sXmvmyKpP+FLOKY8Il7jMw== dependencies: "@mongodb-js/saslprep" "^1.1.5" bson "^6.7.0" @@ -4861,14 +4861,23 @@ mongodb@^5.9.1: optionalDependencies: "@mongodb-js/saslprep" "^1.1.0" -mongoose@^8.4.3: - version "8.4.3" - resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-8.4.3.tgz#f16ea170eeff60a8c8357bed9968e914a4156d1a" - integrity sha512-GxPVLD+I/dxVkgcts2r2QmJJvS62/++btVj3RFt8YnHt+DSOp1Qjj62YEvgZaElwIOTcc4KGJM95X5LlrU1qQg== +mongodb@^6.5.0: + version "6.6.2" + resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-6.6.2.tgz#7ecdd788e9162f6c5726cef40bdd2813cc01e56c" + integrity sha512-ZF9Ugo2JCG/GfR7DEb4ypfyJJyiKbg5qBYKRintebj8+DNS33CyGMkWbrS9lara+u+h+yEOGSRiLhFO/g1s1aw== + dependencies: + "@mongodb-js/saslprep" "^1.1.5" + bson "^6.7.0" + mongodb-connection-string-url "^3.0.0" + +mongoose@^8.6.3: + version "8.6.3" + resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-8.6.3.tgz#fdc1f4defd4de87dc3f5c18aacaf811cafcfcfbe" + integrity sha512-++yRmm7hjMbqVA/8WeiygTnEfrFbiy+OBjQi49GFJIvCQuSYE56myyQWo4j5hbpcHjhHQU8NukMNGTwAWFWjIw== dependencies: bson "^6.7.0" kareem "2.6.3" - mongodb "6.6.2" + mongodb "6.8.0" mpath "0.9.0" mquery "5.0.0" ms "2.1.3" diff --git a/services/web/src/components/form-fields/Phone.js b/services/web/src/components/form-fields/Phone.js index 78c1608f..c3f1d6fb 100644 --- a/services/web/src/components/form-fields/Phone.js +++ b/services/web/src/components/form-fields/Phone.js @@ -4,7 +4,7 @@ import { omit } from 'lodash'; import { Form, Input, Message, Dropdown, Flag } from 'semantic'; -import { COUNTRIES, formatPhone } from 'utils/phone'; +import { COUNTRIES, getFormatLength, formatPhone } from 'utils/phone'; export default class PhoneField extends React.Component { constructor(props) { @@ -27,10 +27,15 @@ export default class PhoneField extends React.Component { } onChange = (evt, { value, ...rest }) => { + const { country } = this.state; + const maxLength = getFormatLength(country); + value = value.replace(/[ ()@.+-]/g, ''); - value = value.replace(/^[01](\d)/, '$1'); + value = value.replace(/^1(\d)/, '$1'); value = value.replace(/[a-z]/gi, ''); value = value.trim(); + value = value.slice(0, maxLength); + if (value) { value = `${this.getPrefix()}${value}`; } diff --git a/services/web/src/modals/EditUser.js b/services/web/src/modals/EditUser.js index f4cb42d8..b1ba2524 100644 --- a/services/web/src/modals/EditUser.js +++ b/services/web/src/modals/EditUser.js @@ -29,7 +29,6 @@ export default class EditUser extends React.Component { } setField = (evt, { name, value }) => { - console.info('ohai', name, value); this.setState({ user: { ...this.state.user, diff --git a/services/web/src/utils/phone/countries-simple.json b/services/web/src/utils/phone/countries-simple.json index 274e18e9..c5177f8f 100644 --- a/services/web/src/utils/phone/countries-simple.json +++ b/services/web/src/utils/phone/countries-simple.json @@ -21,7 +21,7 @@ "jp": { "name": "Japan", "prefix": "+81", - "format": "## #### ####" + "format": "### #### ####" }, "mx": { "name": "Mexico", diff --git a/services/web/src/utils/phone/index.js b/services/web/src/utils/phone/index.js index 87747e87..8118841a 100644 --- a/services/web/src/utils/phone/index.js +++ b/services/web/src/utils/phone/index.js @@ -12,14 +12,18 @@ export function formatPhone(phone, country) { } if (country) { const { prefix, format } = country; - if (prefix === '+1') { - phone = phone.replace(prefix, ''); - } + phone = phone.replace(prefix, ''); phone = applyFormat(phone, format); } return phone; } +export function getFormatLength(code) { + const country = COUNTRIES[code]; + const digits = country.format.replace(/[^#]/g, ''); + return digits.length; +} + function getCountry(phone) { let prefix = ''; const len = Math.min(phone.length, 5);