From 7ec5a16819ee2e304336a4bb9252f44079ac8e70 Mon Sep 17 00:00:00 2001 From: Tommy Poa Date: Mon, 23 Mar 2020 00:36:48 -0700 Subject: [PATCH 01/39] Add material UI. Changing error states. --- components/AuthTextField.js | 19 +++++++++---------- package.json | 1 + yarn.lock | 9 ++++++++- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/components/AuthTextField.js b/components/AuthTextField.js index 0ad21afd..979f62d6 100644 --- a/components/AuthTextField.js +++ b/components/AuthTextField.js @@ -1,11 +1,8 @@ import React from 'react'; +import { TextField } from 'react-native-materialui-textfield'; import Colors from '../assets/Colors'; import { fieldStateColors } from '../lib/authUtils'; -import { - InputNoticeContainer, - TextField, - TextFieldContainer -} from '../styled/auth'; +import { TextFieldContainer, InputNoticeContainer } from '../styled/auth'; import { Caption } from './BaseComponents'; /** @@ -22,16 +19,18 @@ function AuthTextField({ }) { return ( - {fieldType} + {/* {fieldType} */} Date: Mon, 23 Mar 2020 01:06:51 -0700 Subject: [PATCH 02/39] Error working, but working on better UI. --- components/AuthTextField.js | 5 ++- screens/auth/SignUpScreen.js | 69 +++++++++++++++++++++++------------- 2 files changed, 49 insertions(+), 25 deletions(-) diff --git a/components/AuthTextField.js b/components/AuthTextField.js index 979f62d6..c2123dea 100644 --- a/components/AuthTextField.js +++ b/components/AuthTextField.js @@ -15,7 +15,9 @@ function AuthTextField({ value, onBlurCallback, onFocusCallback, - changeTextCallback + changeTextCallback, + error, + errorMessage }) { return ( @@ -30,6 +32,7 @@ function AuthTextField({ onChangeText={changeTextCallback} value={value} tintColor={Colors.primaryGreen} + error={error === '' ? '' : errorMessage} errorColor={Colors.darkerOrange} returnKeyType={'done'} keyboardType={fieldType === 'Phone Number' ? 'numeric' : 'default'} diff --git a/screens/auth/SignUpScreen.js b/screens/auth/SignUpScreen.js index 2736b545..d3662de7 100644 --- a/screens/auth/SignUpScreen.js +++ b/screens/auth/SignUpScreen.js @@ -236,27 +236,6 @@ export default class SignUp extends React.Component { Keyboard.dismiss(); } - handleErrorState(signUpField) { - if ( - signUpField == signUpFields.PHONENUM && - validate('phoneNumber', this.state.phoneNumber) - ) { - return fieldStateColors.ERROR; - } else if ( - signUpField == signUpFields.PASSWORD && - validate('password', this.state.password) - ) { - return fieldStateColors.ERROR; - } else if ( - signUpField == signUpFields.NAME && - !this.state.name.replace(/\s/g, '').length - ) { - return fieldStateColors.ERROR; - } else { - return fieldStateColors.BLURRED; - } - } - onFocus(signUpField) { const { indicators } = this.state; if (indicators[signUpField] == fieldStateColors.ERROR) { @@ -278,6 +257,33 @@ export default class SignUp extends React.Component { }); } + // Typing can only remove errors, not trigger them + updateError(text, signUpField) { + let { nameError, passwordError, phoneNumberError } = this.state; + switch (signUpField) { + case signUpFields.NAME: + if (!text.replace(/\s/g, '').length) { + nameError = 'Not permitted'; + } else { + nameError = ''; + } + break; + case signUpFields.PASSWORD: + passwordError = validate('password', text); + break; + case signUpFields.PHONENUM: + phoneNumberError = validate('phoneNumber', text); + break; + default: + break; + } + this.setState({ + nameError, + passwordError, + phoneNumberError + }); + } + render() { return ( @@ -290,7 +296,12 @@ export default class SignUp extends React.Component { value={this.state.name} onBlurCallback={() => this.onBlur(signUpFields.NAME)} onFocusCallback={() => this.onFocus(signUpFields.NAME)} - changeTextCallback={text => this.setState({ name: text })} + changeTextCallback={text => { + this.updateError(text, signUpFields.NAME); + this.setState({ name: text }); + }} + error={this.state.nameError} + errorMessage="Name cannot be blank" /> this.onBlur(signUpFields.PHONENUM)} onFocusCallback={() => this.onFocus(signUpFields.PHONENUM)} - changeTextCallback={text => this.setState({ phoneNumber: text })} + changeTextCallback={text => { + this.updateError(text, signUpFields.PHONENUM); + this.setState({ phoneNumber: text }); + }} + error={this.state.phoneNumberError} + errorMessage="Must be a valid phone number" /> this.onBlur(signUpFields.PASSWORD)} onFocusCallback={() => this.onFocus(signUpFields.PASSWORD)} - changeTextCallback={text => this.setState({ password: text })} + changeTextCallback={text => { + this.updateError(text, signUpFields.PASSWORD); + this.setState({ password: text }); + }} + error={this.state.passwordError} + errorMessage="Must be 8-20 characters long" /> Date: Tue, 24 Mar 2020 03:32:45 -0700 Subject: [PATCH 03/39] Update prettier rules to match clerk repo, and no longer conflict with VSCode format on save --- prettier.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prettier.config.js b/prettier.config.js index 85b10f97..3d02b43e 100644 --- a/prettier.config.js +++ b/prettier.config.js @@ -1,5 +1,5 @@ module.exports = { - printWidth: 80, + printWidth: 120, tabWidth: 2, singleQuote: true, jsxBracketSameLine: true From 9118740e2e5b36077b6931d41d2836910d9cd33d Mon Sep 17 00:00:00 2001 From: "annie.ro98" Date: Tue, 24 Mar 2020 03:33:40 -0700 Subject: [PATCH 04/39] Update packages - upgrade generator version --- package.json | 2 +- yarn.lock | 521 +++++++++++---------------------------------------- 2 files changed, 110 insertions(+), 413 deletions(-) diff --git a/package.json b/package.json index 575e551a..ba3caced 100644 --- a/package.json +++ b/package.json @@ -85,7 +85,7 @@ "validate.js": "^0.13.1" }, "devDependencies": { - "airtable-schema-generator": "^1.2.5", + "airtable-schema-generator": "^1.3.2", "babel-eslint": "^10.0.3", "babel-preset-expo": "^7.0.0", "eslint": "^5.16.0", diff --git a/yarn.lock b/yarn.lock index 4fb729a2..fc92d994 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1405,10 +1405,10 @@ resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.146.tgz#de0d2c8610012f12a6a796455054cbc654f8fecf" integrity sha512-JzJcmQ/ikHSv7pbvrVNKJU5j9jL9VLf3/gqs048CEnBVVVEv4kve3vLxoPHGvclutS+Il4SBIuQQ087m1eHffw== -"@types/node@^8.0.24": - version "8.10.59" - resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.59.tgz#9e34261f30183f9777017a13d185dfac6b899e04" - integrity sha512-8RkBivJrDCyPpBXhVZcjh7cQxVBSmRk9QM7hOketZzp6Tg79c0N8kkpAIito9bnJ3HCVCHVYz+KHTEbfQNfeVQ== +"@types/mime-types@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@types/mime-types/-/mime-types-2.1.0.tgz#9ca52cda363f699c69466c2a6ccdaad913ea7a73" + integrity sha1-nKUs2jY/aZxpRmwqbM2q2RPqenM= "@types/parse-json@^4.0.0": version "4.0.0" @@ -1558,6 +1558,11 @@ agent-base@4, agent-base@^4.3.0: dependencies: es6-promisify "^5.0.0" +agent-base@5: + version "5.1.1" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-5.1.1.tgz#e8fb3f242959db44d63be665db7a8e739537a32c" + integrity sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g== + agent-base@~4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9" @@ -1572,13 +1577,13 @@ agentkeepalive@^3.4.1: dependencies: humanize-ms "^1.2.1" -airtable-schema-generator@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/airtable-schema-generator/-/airtable-schema-generator-1.2.5.tgz#12c41783b3cf8d1216f3066cc6bd9d56398c8a11" - integrity sha512-1bxkc0xrPfz56c0/eAiuM3sxKN1wAvlNKNsiNuEQzeVHuEvjk3kNLH2aX+6QF6dEV8M20JyH8NalaYE+avgaDQ== +airtable-schema-generator@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/airtable-schema-generator/-/airtable-schema-generator-1.3.2.tgz#0000d85065eb493b8a2c8be8d7a4e2c7111656a8" + integrity sha512-PHHbEuG5ZKmE2YqndNbEpIOC7B44+sO1Gc6savP84Ky+1+njfPGgUWTDv+CFVcrnsRCLP8sMP/U6gIuB9Potww== dependencies: - dotenv-safe "^8.2.0" - nightmare "^3.0.0" + dotenv "^8.2.0" + puppeteer "^2.1.1" airtable@^0.7.1: version "0.7.2" @@ -1802,7 +1807,7 @@ array-filter@~0.0.0: resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-0.0.1.tgz#7da8cf2e26628ed732803581fd21f67cacd2eeec" integrity sha1-fajPLiZijtcygDWB/SH2fKzS7uw= -array-find-index@^1.0.1, array-find-index@^1.0.2: +array-find-index@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" integrity sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E= @@ -2334,19 +2339,6 @@ callsites@^3.0.0: resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== -camelcase-keys@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7" - integrity sha1-MIvur/3ygRkFHvodkyITyRuPkuc= - dependencies: - camelcase "^2.0.0" - map-obj "^1.0.0" - -camelcase@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" - integrity sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8= - camelcase@^4.0.0, camelcase@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" @@ -2632,13 +2624,6 @@ combined-stream@^1.0.6, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" -commander@1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/commander/-/commander-1.0.4.tgz#5edeb1aee23c4fb541a6b70d692abef19669a2d3" - integrity sha1-Xt6xruI8T7VBprcNaSq+8ZZpotM= - dependencies: - keypress "0.1.x" - commander@^2.11.0, commander@^2.19.0, commander@^2.9.0, commander@~2.20.3: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" @@ -2926,13 +2911,6 @@ csstype@^2.2.0: resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.7.tgz#20b0024c20b6718f4eda3853a1f5a1cce7f5e4a5" integrity sha512-9Mcn9sFbGBAdmimWb2gLVDtFJzeKtDGIr76TUqmjZrw9LFXBMSU70lcs+C0/7fyCd6iBDqmksUcCOUIkisPHsQ== -currently-unhandled@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" - integrity sha1-mI3zP+qxke95mmE2nddsF635V+o= - dependencies: - array-find-index "^1.0.1" - cyclist@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" @@ -2969,7 +2947,7 @@ debounce@^1.2.0: resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.0.tgz#44a540abc0ea9943018dc0eaa95cce87f65cd131" integrity sha512-mYtLl1xfZLi1m4RtQYlZgJUNQjl4ZxVnHzIR8nLLgi4q1YT8o/WM+MK/f8yfcc9s5Ir5zRaPZyZU6xs1Syoocg== -debug@2.6.9, debug@^2.1.3, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9: +debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== @@ -2983,6 +2961,13 @@ debug@3.1.0: dependencies: ms "2.0.0" +debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" + integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== + dependencies: + ms "^2.1.1" + debug@^3.1.0, debug@^3.2.6: version "3.2.6" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" @@ -2990,19 +2975,12 @@ debug@^3.1.0, debug@^3.2.6: dependencies: ms "^2.1.1" -debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" - integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== - dependencies: - ms "^2.1.1" - debuglog@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" integrity sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI= -decamelize@^1.1.1, decamelize@^1.1.2, decamelize@^1.2.0: +decamelize@^1.1.1, decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= @@ -3024,13 +3002,6 @@ deep-assign@^3.0.0: dependencies: is-obj "^1.0.0" -deep-defaults@^1.0.3: - version "1.0.5" - resolved "https://registry.yarnpkg.com/deep-defaults/-/deep-defaults-1.0.5.tgz#3bbc6b5773fb07e4cebe3fb69a18c5a9435f140f" - integrity sha512-5ev/sNkiHTmeTqbDJEDgdQa/Ub0eOMQNix9l+dLLGbwOos7/in5HdvHXI014wqxsET4YeJG9Eq4qj0PJRL8rSw== - dependencies: - lodash "^4.17.5" - deep-extend@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" @@ -3046,7 +3017,7 @@ deepmerge@^3.1.0, deepmerge@^3.2.0: resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-3.3.0.tgz#d3c47fd6f3a93d517b14426b0628a17b0125f5f7" integrity sha512-GRQOafGHwMHpjPx9iCvTgpu9NojZ49q794EEL94JVEw6VaeA8XTUyBKvAkOOjBX9oJNiV6G3P+T+tihFjo2TqA== -defaults@^1.0.2, defaults@^1.0.3: +defaults@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" integrity sha1-xlYFHpgX2f8I7YgUd/P+QBnz730= @@ -3176,13 +3147,6 @@ dot-prop@^4.1.0: dependencies: is-obj "^1.0.0" -dotenv-safe@^8.2.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/dotenv-safe/-/dotenv-safe-8.2.0.tgz#8d548c7318a62c09a66c4dc8c31864cc007c78ba" - integrity sha512-uWwWWdUQkSs5a3mySDB22UtNwyEYi0JtEQu+vDzIqr9OjbDdC2Ip13PnSpi/fctqlYmzkxCeabiyCAOROuAIaA== - dependencies: - dotenv "^8.2.0" - dotenv@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-5.0.1.tgz#a5317459bd3d79ab88cff6e44057a6a3fbb1fcef" @@ -3226,35 +3190,11 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= -electron-download@^3.0.1: - version "3.3.0" - resolved "https://registry.yarnpkg.com/electron-download/-/electron-download-3.3.0.tgz#2cfd54d6966c019c4d49ad65fbe65cc9cdef68c8" - integrity sha1-LP1U1pZsAZxNSa1l++Zcyc3vaMg= - dependencies: - debug "^2.2.0" - fs-extra "^0.30.0" - home-path "^1.0.1" - minimist "^1.2.0" - nugget "^2.0.0" - path-exists "^2.1.0" - rc "^1.1.2" - semver "^5.3.0" - sumchecker "^1.2.0" - electron-to-chromium@^1.3.295: version "1.3.306" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.306.tgz#e8265301d053d5f74e36cb876486830261fbe946" integrity sha512-frDqXvrIROoYvikSKTIKbHbzO6M3/qC6kCIt/1FOa9kALe++c4VAJnwjSFvf1tYLEUsP2n9XZ4XSCyqc3l7A/A== -electron@^2.0.18: - version "2.0.18" - resolved "https://registry.yarnpkg.com/electron/-/electron-2.0.18.tgz#52f1b248c3cc013b5a870094de3b6ba5de313a0f" - integrity sha512-PQRHtFvLxHdJzMMIwTddUtkS+Te/fZIs+PHO+zPmTUTBE76V3Od3WRGzMQwiJHxN679licmCKhJpMyxZfDEVWQ== - dependencies: - "@types/node" "^8.0.24" - electron-download "^3.0.1" - extract-zip "^1.0.3" - elegant-spinner@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e" @@ -3284,13 +3224,6 @@ end-of-stream@^1.0.0, end-of-stream@^1.1.0: dependencies: once "^1.4.0" -enqueue@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/enqueue/-/enqueue-1.0.2.tgz#9014e9bce570ee93ca96e6c8e63ad54c192b6bc8" - integrity sha1-kBTpvOVw7pPKlubI5jrVTBkra8g= - dependencies: - sliced "0.0.5" - env-paths@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-1.0.0.tgz#4168133b42bb05c38a35b1ae4397c8298ab369e0" @@ -3353,7 +3286,7 @@ es-to-primitive@^1.2.0: is-date-object "^1.0.1" is-symbol "^1.0.2" -es6-promise@^4.0.3, es6-promise@^4.0.5: +es6-promise@^4.0.3: version "4.2.8" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== @@ -3930,7 +3863,7 @@ extglob@^2.0.4: snapdragon "^0.8.1" to-regex "^3.0.1" -extract-zip@^1.0.3: +extract-zip@^1.6.6: version "1.6.7" resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.6.7.tgz#a840b4b8af6403264c8db57f4f1a74333ef81fe9" integrity sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k= @@ -4149,14 +4082,6 @@ find-parent-dir@^0.3.0: resolved "https://registry.yarnpkg.com/find-parent-dir/-/find-parent-dir-0.3.0.tgz#33c44b429ab2b2f0646299c5f9f718f376ff8d54" integrity sha1-M8RLQpqysvBkYpnF+fcY83b/jVQ= -find-up@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" - integrity sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8= - dependencies: - path-exists "^2.0.0" - pinkie-promise "^2.0.0" - find-up@^2.0.0, find-up@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" @@ -4274,17 +4199,6 @@ from2@^2.1.0: inherits "^2.0.1" readable-stream "^2.0.0" -fs-extra@^0.30.0: - version "0.30.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.30.0.tgz#f233ffcc08d4da7d432daa449776989db1df93f0" - integrity sha1-8jP/zAjU2n1DLapEl3aYnbHfk/A= - dependencies: - graceful-fs "^4.1.2" - jsonfile "^2.1.0" - klaw "^1.0.0" - path-is-absolute "^1.0.0" - rimraf "^2.2.8" - fs-extra@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-1.0.0.tgz#cd3ce5f7e7cb6145883fcae3191e9877f8587950" @@ -4347,11 +4261,6 @@ function-bind@^1.1.1: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== -function-source@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/function-source/-/function-source-0.1.0.tgz#d9104bf3e46788b55468c02bf1b2fabcf8fc19af" - integrity sha1-2RBL8+RniLVUaMAr8bL6vPj8Ga8= - functional-red-black-tree@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" @@ -4424,11 +4333,6 @@ get-own-enumerable-property-symbols@^3.0.0: resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664" integrity sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g== -get-stdin@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" - integrity sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4= - get-stdin@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b" @@ -4677,11 +4581,6 @@ hoist-non-react-statics@^3.0.1, hoist-non-react-statics@^3.1.0, hoist-non-react- dependencies: react-is "^16.7.0" -home-path@^1.0.1: - version "1.0.7" - resolved "https://registry.yarnpkg.com/home-path/-/home-path-1.0.7.tgz#cf77d7339ff3ddc3347a23c52612b1f5e7e56313" - integrity sha512-tM1pVa+u3ZqQwIkXcWfhUlY3HWS3TsnKsfi2OHHvnhkX52s9etyktPyy1rQotkr0euWimChDq+QkQuDe8ngUlQ== - hosted-git-info@^2.1.4, hosted-git-info@^2.7.1, hosted-git-info@^2.8.5: version "2.8.5" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.5.tgz#759cfcf2c4d156ade59b0b2dfabddc42a6b9c70c" @@ -4735,6 +4634,14 @@ https-proxy-agent@^2.2.3: agent-base "^4.3.0" debug "^3.1.0" +https-proxy-agent@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz#702b71fb5520a132a66de1f67541d9e62154d82b" + integrity sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg== + dependencies: + agent-base "5" + debug "4" + human-signals@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" @@ -4849,13 +4756,6 @@ imurmurhash@^0.1.4: resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= -indent-string@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80" - integrity sha1-ji1INIdCEhtKghi3oTfppSBJ3IA= - dependencies: - repeating "^2.0.0" - indent-string@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-3.2.0.tgz#4a5fd6d27cc332f37e5419a504dbb837105c9289" @@ -5109,13 +5009,6 @@ is-extglob@^1.0.0: resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0" integrity sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA= -is-finite@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa" - integrity sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko= - dependencies: - number-is-nan "^1.0.0" - is-fullwidth-code-point@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" @@ -5267,11 +5160,6 @@ is-typedarray@~1.0.0: resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= -is-utf8@^0.2.0: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" - integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI= - is-windows@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" @@ -5835,16 +5723,16 @@ jsdom@^11.5.1: ws "^5.2.0" xml-name-validator "^3.0.0" -jsesc@^0.5.0, jsesc@~0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" - integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= - jsesc@^2.5.1: version "2.5.2" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== +jsesc@~0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" + integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= + json-parse-better-errors@^1.0.0, json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" @@ -5931,11 +5819,6 @@ jsx-ast-utils@^2.2.1: array-includes "^3.0.3" object.assign "^4.1.0" -keypress@0.1.x: - version "0.1.0" - resolved "https://registry.yarnpkg.com/keypress/-/keypress-0.1.0.tgz#4a3188d4291b66b4f65edb99f806aa9ae293592a" - integrity sha1-SjGI1CkbZrT2XtuZ+AaqmuKTWSo= - kind-of@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-1.1.0.tgz#140a3d2d41a36d2efcfa9377b62c24f8495a5c44" @@ -6231,17 +6114,6 @@ listr@^0.14.3: p-map "^2.0.0" rxjs "^6.3.3" -load-json-file@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" - integrity sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA= - dependencies: - graceful-fs "^4.1.2" - parse-json "^2.2.0" - pify "^2.0.0" - pinkie-promise "^2.0.0" - strip-bom "^2.0.0" - load-json-file@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8" @@ -6398,14 +6270,6 @@ loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3 dependencies: js-tokens "^3.0.0 || ^4.0.0" -loud-rejection@^1.0.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f" - integrity sha1-W0b4AUft7leIcPCG0Eghz5mOVR8= - dependencies: - currently-unhandled "^0.4.1" - signal-exit "^3.0.0" - lowercase-keys@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" @@ -6477,11 +6341,6 @@ map-cache@^0.2.2: resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= -map-obj@^1.0.0, map-obj@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" - integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0= - map-visit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" @@ -6522,22 +6381,6 @@ mem@^4.0.0: mimic-fn "^2.0.0" p-is-promise "^2.0.0" -meow@^3.1.0: - version "3.7.0" - resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" - integrity sha1-cstmi0JSKCkKu/qFaJJYcwioAfs= - dependencies: - camelcase-keys "^2.0.0" - decamelize "^1.1.2" - loud-rejection "^1.0.0" - map-obj "^1.0.1" - minimist "^1.1.3" - normalize-package-data "^2.3.4" - object-assign "^4.0.1" - read-pkg-up "^1.0.1" - redent "^1.0.0" - trim-newlines "^1.0.0" - merge-stream@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-1.0.1.tgz#4041202d508a342ba00174008df0c251b8c135e1" @@ -6917,6 +6760,11 @@ mime-db@1.40.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32" integrity sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA== +mime-db@1.43.0: + version "1.43.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58" + integrity sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ== + "mime-db@>= 1.40.0 < 2": version "1.42.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.42.0.tgz#3e252907b4c7adb906597b4b65636272cf9e7bac" @@ -6941,11 +6789,23 @@ mime-types@^2.1.12, mime-types@~2.1.19, mime-types@~2.1.24: dependencies: mime-db "1.40.0" +mime-types@^2.1.25: + version "2.1.26" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06" + integrity sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ== + dependencies: + mime-db "1.43.0" + mime@1.6.0, mime@^1.3.4: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== +mime@^2.0.3: + version "2.4.4" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.4.tgz#bd7b91135fc6b01cde3e9bae33d659b63d8857e5" + integrity sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA== + mimic-fn@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" @@ -6975,7 +6835,7 @@ minimist@0.0.8: resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= -minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0: +minimist@^1.1.1, minimist@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= @@ -7000,13 +6860,6 @@ minizlib@^1.2.1: dependencies: minipass "^2.9.0" -minstache@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/minstache/-/minstache-1.2.0.tgz#ff1cc403ac2844f68dbf18c662129be7eb0efc41" - integrity sha1-/xzEA6woRPaNvxjGYhKb5+sO/EE= - dependencies: - commander "1.0.4" - mississippi@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022" @@ -7076,13 +6929,6 @@ ms@^2.0.0, ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -multiline@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/multiline/-/multiline-1.0.2.tgz#69b1f25ff074d2828904f244ddd06b7d96ef6c93" - integrity sha1-abHyX/B00oKJBPJE3dBrfZbvbJM= - dependencies: - strip-indent "^1.0.0" - mute-stream@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" @@ -7144,26 +6990,6 @@ nice-try@^1.0.4: resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== -nightmare@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/nightmare/-/nightmare-3.0.2.tgz#fb4613259e7b9c05303ae9469e77b1b901351259" - integrity sha512-z6Sr7k71pFcNHFH0orejum9xMzrsdU1lcxlbvNGRsKgDltmu4r52sK5opDnoqfyWS+w9SNthj/4Bbt5zNofzhw== - dependencies: - debug "^2.2.0" - deep-defaults "^1.0.3" - defaults "^1.0.2" - electron "^2.0.18" - enqueue "^1.0.2" - function-source "^0.1.0" - jsesc "^0.5.0" - minstache "^1.2.0" - mkdirp "^0.5.1" - multiline "^1.0.2" - once "^1.3.3" - rimraf "^2.4.3" - sliced "1.0.1" - split2 "^2.0.1" - node-fetch-npm@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/node-fetch-npm/-/node-fetch-npm-2.0.2.tgz#7258c9046182dca345b4208eda918daf33697ff7" @@ -7272,7 +7098,7 @@ normalize-css-color@^1.0.2: resolved "https://registry.yarnpkg.com/normalize-css-color/-/normalize-css-color-1.0.2.tgz#02991e97cccec6623fe573afbbf0de6a1f3e9f8d" integrity sha1-Apkel8zOxmI/5XOvu/Deah8+n40= -normalize-package-data@^2.0.0, normalize-package-data@^2.3.2, normalize-package-data@^2.3.4, normalize-package-data@^2.4.0, normalize-package-data@^2.5.0: +normalize-package-data@^2.0.0, normalize-package-data@^2.3.2, normalize-package-data@^2.4.0, normalize-package-data@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== @@ -7551,19 +7377,6 @@ npmlog@^2.0.4: are-we-there-yet "~1.1.2" gauge "~1.2.5" -nugget@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/nugget/-/nugget-2.0.1.tgz#201095a487e1ad36081b3432fa3cada4f8d071b0" - integrity sha1-IBCVpIfhrTYIGzQy+jytpPjQcbA= - dependencies: - debug "^2.1.3" - minimist "^1.1.0" - pretty-bytes "^1.0.2" - progress-stream "^1.1.0" - request "^2.45.0" - single-line-log "^1.1.2" - throttleit "0.0.2" - nullthrows@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/nullthrows/-/nullthrows-1.1.1.tgz#7818258843856ae971eae4208ad7d7eb19a431b1" @@ -7608,11 +7421,6 @@ object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== -object-keys@~0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-0.4.0.tgz#28a6aae7428dd2c3a92f3d95f21335dd204e0336" - integrity sha1-KKaq50KN0sOpLz2V8hM13SBOAzY= - object-visit@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" @@ -7695,7 +7503,7 @@ on-headers@~1.0.1, on-headers@~1.0.2: resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== -once@^1.3.0, once@^1.3.1, once@^1.3.3, once@^1.4.0, once@~1.4.0: +once@^1.3.0, once@^1.3.1, once@^1.4.0, once@~1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= @@ -8013,13 +7821,6 @@ path-browserify@^1.0.0: resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.0.tgz#40702a97af46ae00b0ea6fa8998c0b03c0af160d" integrity sha512-Hkavx/nY4/plImrZPHRk2CL9vpOymZLgEbMNX1U0bjcBL7QN9wODxyx0yaMZURSQaUtSEvDrfAvxa9oPb0at9g== -path-exists@^2.0.0, path-exists@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" - integrity sha1-D+tsZPD8UY2adU3V77YscCJ2H0s= - dependencies: - pinkie-promise "^2.0.0" - path-exists@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" @@ -8062,15 +7863,6 @@ path-to-regexp@^1.7.0: dependencies: isarray "0.0.1" -path-type@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" - integrity sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE= - dependencies: - graceful-fs "^4.1.2" - pify "^2.0.0" - pinkie-promise "^2.0.0" - path-type@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" @@ -8120,18 +7912,6 @@ pify@^4.0.1: resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== -pinkie-promise@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" - integrity sha1-ITXW36ejWMBprJsXh3YogihFD/o= - dependencies: - pinkie "^2.0.0" - -pinkie@^2.0.0: - version "2.0.4" - resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" - integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA= - pirates@^4.0.0, pirates@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.1.tgz#643a92caf894566f91b2b986d2c66950a8e2fb87" @@ -8241,14 +8021,6 @@ prettier@^1.18.2: resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.18.2.tgz#6823e7c5900017b4bd3acf46fe9ac4b4d7bda9ea" integrity sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw== -pretty-bytes@^1.0.2: - version "1.0.4" - resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-1.0.4.tgz#0a22e8210609ad35542f8c8d5d2159aff0751c84" - integrity sha1-CiLoIQYJrTVUL4yNXSFZr/B1HIQ= - dependencies: - get-stdin "^4.0.1" - meow "^3.1.0" - pretty-format@24.0.0-alpha.6: version "24.0.0-alpha.6" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.0.0-alpha.6.tgz#25ad2fa46b342d6278bf241c5d2114d4376fbac1" @@ -8295,15 +8067,7 @@ process@~0.5.1: resolved "https://registry.yarnpkg.com/process/-/process-0.5.2.tgz#1638d8a8e34c2f440a91db95ab9aeb677fc185cf" integrity sha1-FjjYqONML0QKkduVq5rrZ3/Bhc8= -progress-stream@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/progress-stream/-/progress-stream-1.2.0.tgz#2cd3cfea33ba3a89c9c121ec3347abe9ab125f77" - integrity sha1-LNPP6jO6OonJwSHsM0er6asSX3c= - dependencies: - speedometer "~0.1.2" - through2 "~0.2.3" - -progress@^2.0.0: +progress@^2.0.0, progress@^2.0.1: version "2.0.3" resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== @@ -8364,6 +8128,11 @@ protoduck@^5.0.1: dependencies: genfun "^5.0.0" +proxy-from-env@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + prr@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" @@ -8414,6 +8183,22 @@ punycode@^2.1.0, punycode@^2.1.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== +puppeteer@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-2.1.1.tgz#ccde47c2a688f131883b50f2d697bd25189da27e" + integrity sha512-LWzaDVQkk1EPiuYeTOj+CZRIjda4k2s5w4MK4xoH2+kgWV/SDlkYHmxatDdtYrciHUKSXTsGgPgPP8ILVdBsxg== + dependencies: + "@types/mime-types" "^2.1.0" + debug "^4.1.0" + extract-zip "^1.6.6" + https-proxy-agent "^4.0.0" + mime "^2.0.3" + mime-types "^2.1.25" + progress "^2.0.1" + proxy-from-env "^1.0.0" + rimraf "^2.6.1" + ws "^6.1.0" + qrcode-terminal@^0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz#bb5b699ef7f9f0505092a3748be4464fe71b5819" @@ -8462,7 +8247,7 @@ range-parser@~1.2.1: resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== -rc@^1.0.1, rc@^1.1.2, rc@^1.1.6, rc@^1.2.7: +rc@^1.0.1, rc@^1.1.6, rc@^1.2.7: version "1.2.8" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== @@ -8872,14 +8657,6 @@ read-package-tree@^5.3.1: readdir-scoped-modules "^1.0.0" util-promisify "^2.1.0" -read-pkg-up@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" - integrity sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI= - dependencies: - find-up "^1.0.0" - read-pkg "^1.0.0" - read-pkg-up@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" @@ -8896,15 +8673,6 @@ read-pkg-up@^4.0.0: find-up "^3.0.0" read-pkg "^3.0.0" -read-pkg@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28" - integrity sha1-9f+qXs0pyzHAR0vKfXVra7KePyg= - dependencies: - load-json-file "^1.0.0" - normalize-package-data "^2.3.2" - path-type "^1.0.0" - read-pkg@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8" @@ -8930,7 +8698,7 @@ read@1, read@~1.0.1, read@~1.0.7: dependencies: mute-stream "~0.0.4" -"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.6, readable-stream@~2.3.6: +"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.3.6, readable-stream@~2.3.6: version "2.3.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== @@ -8943,6 +8711,19 @@ read@1, read@~1.0.1, read@~1.0.7: string_decoder "~1.1.1" util-deprecate "~1.0.1" +readable-stream@^2.2.2: + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + readable-stream@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.4.0.tgz#a51c26754658e0a3c21dbf59163bd45ba6f447fc" @@ -8952,7 +8733,7 @@ readable-stream@^3.4.0: string_decoder "^1.1.1" util-deprecate "^1.0.1" -readable-stream@~1.1.10, readable-stream@~1.1.9: +readable-stream@~1.1.10: version "1.1.14" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" integrity sha1-fPTFTvZI44EwhMY23SB54WbAgdk= @@ -8984,14 +8765,6 @@ reanimated-bottom-sheet@^1.0.0-alpha.15: resolved "https://registry.yarnpkg.com/reanimated-bottom-sheet/-/reanimated-bottom-sheet-1.0.0-alpha.16.tgz#00bf895a788df2fd16d25a570afdc8ceae5ee8c0" integrity sha512-wAdyCzXbkshJp4hHkXseKC2H59Hn6c/VrH98Bn74C8Mb8ZmRqQLF9KYiOMWIMFV9fq4LzywdKfIjrNfpmrGaMA== -redent@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" - integrity sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94= - dependencies: - indent-string "^2.1.0" - strip-indent "^1.0.1" - regenerate-unicode-properties@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.1.0.tgz#ef51e0f0ea4ad424b77bf7cb41f3e015c70a3f0e" @@ -9095,13 +8868,6 @@ repeat-string@^1.5.2, repeat-string@^1.6.1: resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= -repeating@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" - integrity sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo= - dependencies: - is-finite "^1.0.0" - request-promise-core@1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.2.tgz#339f6aababcafdb31c799ff158700336301d3346" @@ -9118,7 +8884,7 @@ request-promise-native@^1.0.5: stealthy-require "^1.1.1" tough-cookie "^2.3.3" -request@2.88.0, request@^2.45.0, request@^2.87.0, request@^2.88.0: +request@2.88.0, request@^2.87.0, request@^2.88.0: version "2.88.0" resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== @@ -9231,7 +8997,7 @@ retry@^0.12.0: resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= -rimraf@2, rimraf@^2.2.8, rimraf@^2.4.3, rimraf@^2.5.2, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3: +rimraf@2, rimraf@^2.5.2, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== @@ -9547,13 +9313,6 @@ simple-swizzle@^0.2.2: dependencies: is-arrayish "^0.3.1" -single-line-log@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/single-line-log/-/single-line-log-1.1.2.tgz#c2f83f273a3e1a16edb0995661da0ed5ef033364" - integrity sha1-wvg/Jzo+GhbtsJlWYdoO1e8DM2Q= - dependencies: - string-width "^1.0.1" - sisteransi@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.3.tgz#98168d62b79e3a5e758e27ae63c4a053d748f4eb" @@ -9583,16 +9342,6 @@ slice-ansi@^2.1.0: astral-regex "^1.0.0" is-fullwidth-code-point "^2.0.0" -sliced@0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/sliced/-/sliced-0.0.5.tgz#5edc044ca4eb6f7816d50ba2fc63e25d8fe4707f" - integrity sha1-XtwETKTrb3gW1Qui/GPiXY/kcH8= - -sliced@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/sliced/-/sliced-1.0.1.tgz#0b3a662b5d04c3177b1926bea82b03f837a2ef41" - integrity sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E= - slide@^1.1.5, slide@^1.1.6, slide@~1.1.3, slide@~1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707" @@ -9722,11 +9471,6 @@ spdx-license-ids@^3.0.0: resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz#3694b5804567a458d3c8045842a6358632f62654" integrity sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q== -speedometer@~0.1.2: - version "0.1.4" - resolved "https://registry.yarnpkg.com/speedometer/-/speedometer-0.1.4.tgz#9876dbd2a169d3115402d48e6ea6329c8816a50d" - integrity sha1-mHbb0qFp0xFUAtSObqYynIgWpQ0= - split-on-first@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f" @@ -9739,13 +9483,6 @@ split-string@^3.0.1, split-string@^3.0.2: dependencies: extend-shallow "^3.0.0" -split2@^2.0.1: - version "2.2.0" - resolved "https://registry.yarnpkg.com/split2/-/split2-2.2.0.tgz#186b2575bcf83e85b7d18465756238ee4ee42493" - integrity sha512-RAb22TG39LhI31MbreBgIuKiIKhVsawfTgEGqKHTK87aG+ul/PB8Sqoi3I7kVdRWiCfrKxK3uo4/YUkpNvhPbw== - dependencies: - through2 "^2.0.2" - sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -9958,13 +9695,6 @@ strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" -strip-bom@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" - integrity sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4= - dependencies: - is-utf8 "^0.2.0" - strip-bom@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" @@ -9980,13 +9710,6 @@ strip-final-newline@^2.0.0: resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== -strip-indent@^1.0.0, strip-indent@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2" - integrity sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI= - dependencies: - get-stdin "^4.0.1" - strip-json-comments@^2.0.1, strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" @@ -10008,14 +9731,6 @@ styled-components@^5.0.1: shallowequal "^1.1.0" supports-color "^5.5.0" -sumchecker@^1.2.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-1.3.1.tgz#79bb3b4456dd04f18ebdbc0d703a1d1daec5105d" - integrity sha1-ebs7RFbdBPGOvbwNcDodHa7FEF0= - dependencies: - debug "^2.2.0" - es6-promise "^4.0.5" - supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" @@ -10110,12 +9825,7 @@ throat@^4.0.0, throat@^4.1.0: resolved "https://registry.yarnpkg.com/throat/-/throat-4.1.0.tgz#89037cbc92c56ab18926e6ba4cbb200e15672a6a" integrity sha1-iQN8vJLFarGJJua6TLsgDhVnKmo= -throttleit@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-0.0.2.tgz#cfedf88e60c00dd9697b61fdd2a8343a9b680eaf" - integrity sha1-z+34jmDADdlpe2H90qg0OptoDq8= - -through2@^2.0.0, through2@^2.0.2: +through2@^2.0.0: version "2.0.5" resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== @@ -10123,14 +9833,6 @@ through2@^2.0.0, through2@^2.0.2: readable-stream "~2.3.6" xtend "~4.0.1" -through2@~0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/through2/-/through2-0.2.3.tgz#eb3284da4ea311b6cc8ace3653748a52abf25a3f" - integrity sha1-6zKE2k6jEbbMis42U3SKUqvyWj8= - dependencies: - readable-stream "~1.1.9" - xtend "~2.1.1" - "through@>=2.2.7 <3", through@^2.3.6: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" @@ -10243,11 +9945,6 @@ tr46@^1.0.1: dependencies: punycode "^2.1.0" -trim-newlines@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" - integrity sha1-WIeWa7WCpFA6QetST301ARgVphM= - tslib@^1.9.0: version "1.10.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" @@ -10796,6 +10493,13 @@ ws@^5.2.0: dependencies: async-limiter "~1.0.0" +ws@^6.1.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" + integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA== + dependencies: + async-limiter "~1.0.0" + xcode@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/xcode/-/xcode-2.0.0.tgz#134f1f94c26fbfe8a9aaa9724bfb2772419da1a2" @@ -10865,13 +10569,6 @@ xtend@^4.0.0, xtend@~4.0.1: resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== -xtend@~2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-2.1.2.tgz#6efecc2a4dad8e6962c4901b337ce7ba87b5d28b" - integrity sha1-bv7MKk2tjmlixJAbM3znuoe10os= - dependencies: - object-keys "~0.4.0" - y18n@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" From 59a5081770f425c16062fa73004cd4acb54bfd46 Mon Sep 17 00:00:00 2001 From: "annie.ro98" Date: Wed, 25 Mar 2020 01:20:33 -0700 Subject: [PATCH 05/39] Update Airtable schema --- .env.example | 8 +- .gitignore | 8 +- lib/airtable/airtable.js | 288 ++++---- lib/airtable/request.js | 256 +++++-- lib/airtable/schema.js | 241 ++++--- lib/airtable/schemaRaw.json | 1328 ----------------------------------- lib/common.js | 2 +- package.json | 12 +- 8 files changed, 474 insertions(+), 1669 deletions(-) delete mode 100644 lib/airtable/schemaRaw.json diff --git a/.env.example b/.env.example index a05d7273..777897d4 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,7 @@ -AIRTABLE_BASE_ID='your-API-key-here' -AIRTABLE_API_KEY='keyioENo5dwh9Czt9' +REACT_APP_AIRTABLE_API_KEY={AIRTABLE API KEY} +AIRTABLE_BASE_ID='app4fXK49bqcjDMEo' + +AIRTABLE_EMAIL='your-airtable-email' +AIRTABLE_PASSWORD='your-airtable-password' + IMG_API_KEY='image-API-key-here' \ No newline at end of file diff --git a/.gitignore b/.gitignore index 054802f7..50f03c73 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,16 @@ .expo/* node_modules/**/* -# Deprecated env file +# Deprecated env files environment.js +.airtable-schema-generator.env +# Only used for manual +schemaRaw.json # env files .env.development .env.production -.airtable-schema-generator.env + + package-lock.json *.DS_Store diff --git a/lib/airtable/airtable.js b/lib/airtable/airtable.js index 0d711ebd..90e9c388 100644 --- a/lib/airtable/airtable.js +++ b/lib/airtable/airtable.js @@ -1,5 +1,4 @@ /* eslint no-restricted-imports: 0 */ - /* THIS IS A GENERATED FILE Changes might be overwritten in the future, edit with caution! @@ -9,7 +8,6 @@ If you're adding a new function: make sure you add a corresponding test (at least 1) for it in airtable.spec.js */ - import Airtable from 'airtable'; import { Columns } from './schema'; @@ -17,7 +15,7 @@ const BASE_ID = 'app4fXK49bqcjDMEo'; const VIEW = 'Grid view'; const ENDPOINT_URL = 'https://api.airtable.com'; -const apiKey = process.env.AIRTABLE_API_KEY; +const apiKey = process.env.REACT_APP_AIRTABLE_API_KEY; Airtable.configure({ endpointUrl: ENDPOINT_URL, @@ -29,199 +27,167 @@ const base = Airtable.base(BASE_ID); // Transformation Utilities const fromAirtableFormat = (record, table) => { - // the `Columns` table in schema.js maps from JS name to Airtable name. - // Inverting this table lets us go the other way around - const invertedColumns = {}; - Object.keys(Columns[table]).forEach(key => { - invertedColumns[Columns[table][key]] = key; - }); + const columns = Columns[table]; + if (!columns) { + throw new Error(`Error converting record from Airtable. Could not find table: ${table}`); + } + + // Invert columns object + const invertedColumns = Object.keys(columns).reduce( + (obj, key) => ({ + ...obj, + [columns[key].name]: { name: key, type: columns[key].type } + }), + {} + ); // Create a new object mapping each record attribute - const newRecord = {}; - Object.keys(record).forEach(origName => { - const jsFormattedName = invertedColumns[origName]; - newRecord[jsFormattedName] = record[origName]; - }); - return newRecord; + return Object.keys(record).reduce((obj, origColumn) => { + const jsFormattedName = invertedColumns[origColumn]; + + if (!jsFormattedName) { + throw new Error( + `Error converting ${table} record from Airtable. Could not find column of name "${origColumn}" in local copy of schema.js. Please run the schema generator again to get updates` + ); + } + + let value = record[origColumn]; + + // Unwrap array if it's a single foreign key relationship + if (jsFormattedName.type === 'foreignKey-one') { + [value] = value; // Array Destructuring + } + + return { + ...obj, + [jsFormattedName.name]: value + }; + }, {}); }; const toAirtableFormat = (record, table) => { const columns = Columns[table]; + if (!columns) { + throw new Error(`Error converting record for Airtable. Could not find table: ${table}`); + } - const newRecord = {}; - Object.keys(record).forEach(jsFormattedName => { - const origName = columns[jsFormattedName]; - newRecord[origName] = record[jsFormattedName]; - }); - return newRecord; + return Object.keys(record).reduce((obj, jsFormattedColumnName) => { + const origColumn = columns[jsFormattedColumnName]; + + if (!origColumn) { + throw new Error( + `Error converting ${table} record from Airtable. Could not find column of name "${jsFormattedColumnName}" in local copy of schema.js. Please check your "update" and "create" calls and ensure that your column names exist. If that doesn't work, run the schema generator again to get updates.` + ); + } + + let value = record[jsFormattedColumnName]; + if (origColumn.type === 'foreignKey-one') { + value = [value]; // rewrap array if it's a single foreign key relationship + } + + return { ...obj, [origColumn.name]: value }; + }, {}); }; // ******** CRUD ******** // // Given a table and a record object, create a record on Airtable. function createRecord(table, record) { - return new Promise(function(resolve, reject) { - const transformedRecord = toAirtableFormat(record, table); - base(table).create([{ fields: transformedRecord }], function(err, records) { - if (err) { - reject(err); - return; - } + const transformedRecord = toAirtableFormat(record, table); + return new Promise(function process(resolve, reject) { + base(table) + .create([{ fields: transformedRecord }]) + .then(records => { + resolve(records[0].getId()); + }) + .catch(err => reject(err)); + }); +} - const expectedLen = 1; - if (records.length !== expectedLen) { - reject( - new Error( - `${records.length} records returned from creating 1 record. Expected: ${expectedLen}` - ) - ); - return; +function getAllRecords(table, filterByFormula = '', sort = []) { + return base(table) + .select({ + view: VIEW, + filterByFormula, + sort + }) + .all() + .then(records => { + if (records === null || records.length < 1) { + const msg = `No record was retrieved using this ${table}.`; + throw new Error(msg); } - resolve(records[0].getId()); + return records.map(record => fromAirtableFormat(record.fields, table)); + }) + .catch(err => { + throw err; }); - }); -} - -// TODO pagination? -// TODO: current implementation only fetches the first page -function getAllRecords(table) { - return new Promise(function(resolve, reject) { - base(table) - .select({ - view: VIEW - }) - .eachPage( - function page(records, fetchNextPage) { - if (records === null || records.length < 1) { - const msg = `No record was retrieved using this ${table}.`; - reject(msg); - return; - } - - resolve( - records.map(record => fromAirtableFormat(record.fields, table)) - ); - - // To fetch the next page of records, call `fetchNextPage`. - // If there are more records, `page` will get called again. - // If there are no more records, `done` will get called. - fetchNextPage(); - }, - function done(err) { - if (err) { - reject(err); - } - } - ); - }); } // Given a table and record ID, return the associated record object using a Promise. function getRecordById(table, id) { - return new Promise(function(resolve, reject) { - base(table).find(id, function(err, record) { - if (err) { - reject(err); - return; - } - - resolve(fromAirtableFormat(record.fields, table)); + return base(table) + .find(id) + .then(record => { + return fromAirtableFormat(record.fields, table); + }) + .catch(err => { + throw err; }); - }); } -// TODO: current implementation only returns the first page /* Given the desired table, field type (column), and field ('nick wong' or 'aivant@pppower.io'), return the associated record object. */ -function getRecordsByAttribute(table, fieldType, field) { - return new Promise(function(resolve, reject) { - base(table) - .select({ - view: VIEW, - filterByFormula: `{${fieldType}}='${field}'` - }) - .firstPage((err, records) => { - if (err) { - reject(err); - return; - } - if (!records || records.length < 1) { - console.log(`No record was retrieved using this ${fieldType}.`); - resolve([]); - // No need for this to throw an error, sometimes there's just no values - // reject(new Error(`No record was retrieved using this ${fieldType}.`)); - return; - } - - resolve( - records.map(record => fromAirtableFormat(record.fields, table)) - ); - }); - }); +function getRecordsByAttribute(table, fieldType, field, sort = []) { + return base(table) + .select({ + view: VIEW, + filterByFormula: `{${fieldType}}='${field}'`, + sort + }) + .all() + .then(records => { + if (!records || records.length < 1) { + // No need for this to throw an error, sometimes there're just no values + return []; + } + + return records.map(record => fromAirtableFormat(record.fields, table)); + }) + .catch(err => { + throw err; + }); } // Given a table and a record object, update a record on Airtable. function updateRecord(table, id, updatedRecord) { - return new Promise(function(resolve, reject) { - const transformedRecord = toAirtableFormat(updatedRecord, table); - base(table).update( - [ - { - id, - fields: transformedRecord - } - ], - function(err, records) { - if (err) { - reject(err); - return; - } - - const expectedLen = 1; - if (records.length !== expectedLen) { - reject( - new Error( - `${records.length} records returned from creating 1 record. Expected: ${expectedLen}` - ) - ); - return; - } - - resolve(records[0].id); // TODO + const transformedRecord = toAirtableFormat(updatedRecord, table); + return base(table) + .update([ + { + id, + fields: transformedRecord } - ); - }); + ]) + .then(records => { + return records[0].id; + }) + .catch(err => { + throw err; + }); } function deleteRecord(table, id) { - return new Promise(function(resolve, reject) { - base(table).destroy([id], function(err, deletedRecords) { - if (err) { - reject(err); - return; - } - const expectedLen = 1; - if (deletedRecords.length !== expectedLen) { - reject( - new Error( - `${deletedRecords.length} records returned from deleting ${expectedLen} record(s). Expected: ${expectedLen}` - ) - ); - return; - } - - resolve(deletedRecords[0].fields); + return base(table) + .destroy([id]) + .then(records => { + return records[0].fields; + }) + .catch(err => { + throw err; }); - }); } -export { - createRecord, - getAllRecords, - getRecordById, - getRecordsByAttribute, - updateRecord, - deleteRecord -}; +export { createRecord, getAllRecords, getRecordById, getRecordsByAttribute, updateRecord, deleteRecord }; diff --git a/lib/airtable/request.js b/lib/airtable/request.js index 239f9d39..1a780b01 100644 --- a/lib/airtable/request.js +++ b/lib/airtable/request.js @@ -11,15 +11,15 @@ */ -import { Tables, Columns } from './schema'; import { createRecord, - updateRecord, + deleteRecord, getAllRecords, - getRecordsByAttribute, getRecordById, - deleteRecord + getRecordsByAttribute, + updateRecord } from './airtable'; +import { Columns, Tables } from './schema'; /* ******* CREATE RECORDS ******* @@ -37,20 +37,36 @@ export const createClerks = async record => { return createRecord(Tables.Clerks, record); }; -export const createTransactions = async record => { - return createRecord(Tables.Transactions, record); +export const createStores = async record => { + return createRecord(Tables.Stores, record); +}; + +export const createProducts = async record => { + return createRecord(Tables.Products, record); }; export const createLineItems = async record => { return createRecord(Tables.LineItems, record); }; -export const createStores = async record => { - return createRecord(Tables.Stores, record); +export const createTransactions = async record => { + return createRecord(Tables.Transactions, record); }; -export const createProducts = async record => { - return createRecord(Tables.Products, record); +export const createRecipes = async record => { + return createRecord(Tables.Recipes, record); +}; + +export const createResources = async record => { + return createRecord(Tables.Resources, record); +}; + +export const createCustomerFeedback = async record => { + return createRecord(Tables.CustomerFeedback, record); +}; + +export const createClerkFeedback = async record => { + return createRecord(Tables.ClerkFeedback, record); }; export const createNews = async record => { @@ -61,10 +77,6 @@ export const createReceipts = async record => { return createRecord(Tables.Receipts, record); }; -export const createRecipes = async record => { - return createRecord(Tables.Recipes, record); -}; - /* ******* READ RECORDS ******* */ @@ -73,88 +85,173 @@ export const getCustomersById = async id => { return getRecordById(Tables.Customers, id); }; -export const getAllCustomerss = async () => { - return getAllRecords(Tables.Customers); +export const getCustomersByIds = async ids => { + const formula = `OR(${ids.reduce((f, id) => `${f} {ID}='${id}',`, '')} 1 < 0)`; + return getAllRecords(Tables.Customers, formula); +}; + +export const getAllCustomers = async (filterByFormula = '', sort = []) => { + return getAllRecords(Tables.Customers, filterByFormula, sort); }; -export const getCustomerssByPhoneNumber = async value => { - return getRecordsByAttribute( - Tables.Customers, - Columns[Tables.Customers].phoneNumber, - value - ); +export const getCustomersByPhoneNumber = async (value, sort = []) => { + return getRecordsByAttribute(Tables.Customers, Columns[Tables.Customers].phoneNumber.name, value, sort); }; export const getPushTokensById = async id => { return getRecordById(Tables.PushTokens, id); }; -export const getAllPushTokenss = async () => { - return getAllRecords(Tables.PushTokens); +export const getPushTokensByIds = async ids => { + const formula = `OR(${ids.reduce((f, id) => `${f} {ID}='${id}',`, '')} 1 < 0)`; + return getAllRecords(Tables.PushTokens, formula); +}; + +export const getAllPushTokens = async (filterByFormula = '', sort = []) => { + return getAllRecords(Tables.PushTokens, filterByFormula, sort); }; export const getClerksById = async id => { return getRecordById(Tables.Clerks, id); }; -export const getAllClerkss = async () => { - return getAllRecords(Tables.Clerks); +export const getClerksByIds = async ids => { + const formula = `OR(${ids.reduce((f, id) => `${f} {ID}='${id}',`, '')} 1 < 0)`; + return getAllRecords(Tables.Clerks, formula); }; -export const getTransactionsById = async id => { - return getRecordById(Tables.Transactions, id); +export const getAllClerks = async (filterByFormula = '', sort = []) => { + return getAllRecords(Tables.Clerks, filterByFormula, sort); }; -export const getAllTransactionss = async () => { - return getAllRecords(Tables.Transactions); +export const getStoresById = async id => { + return getRecordById(Tables.Stores, id); +}; + +export const getStoresByIds = async ids => { + const formula = `OR(${ids.reduce((f, id) => `${f} {ID}='${id}',`, '')} 1 < 0)`; + return getAllRecords(Tables.Stores, formula); +}; + +export const getAllStores = async (filterByFormula = '', sort = []) => { + return getAllRecords(Tables.Stores, filterByFormula, sort); +}; + +export const getProductsById = async id => { + return getRecordById(Tables.Products, id); +}; + +export const getProductsByIds = async ids => { + const formula = `OR(${ids.reduce((f, id) => `${f} {ID}='${id}',`, '')} 1 < 0)`; + return getAllRecords(Tables.Products, formula); +}; + +export const getAllProducts = async (filterByFormula = '', sort = []) => { + return getAllRecords(Tables.Products, filterByFormula, sort); }; export const getLineItemsById = async id => { return getRecordById(Tables.LineItems, id); }; -export const getAllLineItemss = async () => { - return getAllRecords(Tables.LineItems); +export const getLineItemsByIds = async ids => { + const formula = `OR(${ids.reduce((f, id) => `${f} {ID}='${id}',`, '')} 1 < 0)`; + return getAllRecords(Tables.LineItems, formula); }; -export const getStoresById = async id => { - return getRecordById(Tables.Stores, id); +export const getAllLineItems = async (filterByFormula = '', sort = []) => { + return getAllRecords(Tables.LineItems, filterByFormula, sort); }; -export const getAllStoress = async () => { - return getAllRecords(Tables.Stores); +export const getTransactionsById = async id => { + return getRecordById(Tables.Transactions, id); }; -export const getProductsById = async id => { - return getRecordById(Tables.Products, id); +export const getTransactionsByIds = async ids => { + const formula = `OR(${ids.reduce((f, id) => `${f} {ID}='${id}',`, '')} 1 < 0)`; + return getAllRecords(Tables.Transactions, formula); +}; + +export const getAllTransactions = async (filterByFormula = '', sort = []) => { + return getAllRecords(Tables.Transactions, filterByFormula, sort); +}; + +export const getRecipesById = async id => { + return getRecordById(Tables.Recipes, id); +}; + +export const getRecipesByIds = async ids => { + const formula = `OR(${ids.reduce((f, id) => `${f} {ID}='${id}',`, '')} 1 < 0)`; + return getAllRecords(Tables.Recipes, formula); +}; + +export const getAllRecipes = async (filterByFormula = '', sort = []) => { + return getAllRecords(Tables.Recipes, filterByFormula, sort); +}; + +export const getResourcesById = async id => { + return getRecordById(Tables.Resources, id); }; -export const getAllProductss = async () => { - return getAllRecords(Tables.Products); +export const getResourcesByIds = async ids => { + const formula = `OR(${ids.reduce((f, id) => `${f} {ID}='${id}',`, '')} 1 < 0)`; + return getAllRecords(Tables.Resources, formula); +}; + +export const getAllResources = async (filterByFormula = '', sort = []) => { + return getAllRecords(Tables.Resources, filterByFormula, sort); +}; + +export const getCustomerFeedbackById = async id => { + return getRecordById(Tables.CustomerFeedback, id); +}; + +export const getCustomerFeedbacksByIds = async ids => { + const formula = `OR(${ids.reduce((f, id) => `${f} {ID}='${id}',`, '')} 1 < 0)`; + return getAllRecords(Tables.CustomerFeedback, formula); +}; + +export const getAllCustomerFeedbacks = async (filterByFormula = '', sort = []) => { + return getAllRecords(Tables.CustomerFeedback, filterByFormula, sort); +}; + +export const getClerkFeedbackById = async id => { + return getRecordById(Tables.ClerkFeedback, id); +}; + +export const getClerkFeedbacksByIds = async ids => { + const formula = `OR(${ids.reduce((f, id) => `${f} {ID}='${id}',`, '')} 1 < 0)`; + return getAllRecords(Tables.ClerkFeedback, formula); +}; + +export const getAllClerkFeedbacks = async (filterByFormula = '', sort = []) => { + return getAllRecords(Tables.ClerkFeedback, filterByFormula, sort); }; export const getNewsById = async id => { return getRecordById(Tables.News, id); }; -export const getAllNewss = async () => { - return getAllRecords(Tables.News); +export const getNewsByIds = async ids => { + const formula = `OR(${ids.reduce((f, id) => `${f} {ID}='${id}',`, '')} 1 < 0)`; + return getAllRecords(Tables.News, formula); +}; + +export const getAllNews = async (filterByFormula = '', sort = []) => { + return getAllRecords(Tables.News, filterByFormula, sort); }; export const getReceiptsById = async id => { return getRecordById(Tables.Receipts, id); }; -export const getAllReceiptss = async () => { - return getAllRecords(Tables.Receipts); +export const getReceiptsByIds = async ids => { + const formula = `OR(${ids.reduce((f, id) => `${f} {ID}='${id}',`, '')} 1 < 0)`; + return getAllRecords(Tables.Receipts, formula); }; -export const getRecipesById = async id => { - return getRecordById(Tables.Recipes, id); -}; - -export const getAllRecipess = async () => { - return getAllRecords(Tables.Recipes); +export const getAllReceipts = async (filterByFormula = '', sort = []) => { + return getAllRecords(Tables.Receipts, filterByFormula, sort); }; /* @@ -173,20 +270,36 @@ export const updateClerks = async (id, recordUpdates) => { return updateRecord(Tables.Clerks, id, recordUpdates); }; -export const updateTransactions = async (id, recordUpdates) => { - return updateRecord(Tables.Transactions, id, recordUpdates); +export const updateStores = async (id, recordUpdates) => { + return updateRecord(Tables.Stores, id, recordUpdates); +}; + +export const updateProducts = async (id, recordUpdates) => { + return updateRecord(Tables.Products, id, recordUpdates); }; export const updateLineItems = async (id, recordUpdates) => { return updateRecord(Tables.LineItems, id, recordUpdates); }; -export const updateStores = async (id, recordUpdates) => { - return updateRecord(Tables.Stores, id, recordUpdates); +export const updateTransactions = async (id, recordUpdates) => { + return updateRecord(Tables.Transactions, id, recordUpdates); }; -export const updateProducts = async (id, recordUpdates) => { - return updateRecord(Tables.Products, id, recordUpdates); +export const updateRecipes = async (id, recordUpdates) => { + return updateRecord(Tables.Recipes, id, recordUpdates); +}; + +export const updateResources = async (id, recordUpdates) => { + return updateRecord(Tables.Resources, id, recordUpdates); +}; + +export const updateCustomerFeedback = async (id, recordUpdates) => { + return updateRecord(Tables.CustomerFeedback, id, recordUpdates); +}; + +export const updateClerkFeedback = async (id, recordUpdates) => { + return updateRecord(Tables.ClerkFeedback, id, recordUpdates); }; export const updateNews = async (id, recordUpdates) => { @@ -197,10 +310,6 @@ export const updateReceipts = async (id, recordUpdates) => { return updateRecord(Tables.Receipts, id, recordUpdates); }; -export const updateRecipes = async (id, recordUpdates) => { - return updateRecord(Tables.Recipes, id, recordUpdates); -}; - /* ******* DELETE RECORDS ******* */ @@ -214,24 +323,33 @@ export const deletePushTokens = async id => { export const deleteClerks = async id => { return deleteRecord(Tables.Clerks, id); }; -export const deleteTransactions = async id => { - return deleteRecord(Tables.Transactions, id); -}; -export const deleteLineItems = async id => { - return deleteRecord(Tables.LineItems, id); -}; export const deleteStores = async id => { return deleteRecord(Tables.Stores, id); }; export const deleteProducts = async id => { return deleteRecord(Tables.Products, id); }; +export const deleteLineItems = async id => { + return deleteRecord(Tables.LineItems, id); +}; +export const deleteTransactions = async id => { + return deleteRecord(Tables.Transactions, id); +}; +export const deleteRecipes = async id => { + return deleteRecord(Tables.Recipes, id); +}; +export const deleteResources = async id => { + return deleteRecord(Tables.Resources, id); +}; +export const deleteCustomerFeedback = async id => { + return deleteRecord(Tables.CustomerFeedback, id); +}; +export const deleteClerkFeedback = async id => { + return deleteRecord(Tables.ClerkFeedback, id); +}; export const deleteNews = async id => { return deleteRecord(Tables.News, id); }; export const deleteReceipts = async id => { return deleteRecord(Tables.Receipts, id); }; -export const deleteRecipes = async id => { - return deleteRecord(Tables.Recipes, id); -}; diff --git a/lib/airtable/schema.js b/lib/airtable/schema.js index 634cc92f..450ccecb 100644 --- a/lib/airtable/schema.js +++ b/lib/airtable/schema.js @@ -7,129 +7,162 @@ export const Tables = { Customers: 'Customers', PushTokens: 'Push Tokens', Clerks: 'Clerks', - Transactions: 'Transactions', - LineItems: 'Line Items', Stores: 'Stores', Products: 'Products', + LineItems: 'Line Items', + Transactions: 'Transactions', + Recipes: 'Recipes', + Resources: 'Resources', + CustomerFeedback: 'Customer Feedback', + ClerkFeedback: 'Clerk Feedback', News: 'News', - Receipts: 'Receipts', - Recipes: 'Recipes' + Receipts: 'Receipts' }; export const Columns = { Customers: { - primaryKey: `Primary Key`, - firstName: `First Name`, - lastName: `Last Name`, - zipcode: `Zipcode`, - phoneNumber: `Phone Number`, - password: `Password`, - points: `Points`, - unlockedRewards: `Unlocked Rewards`, - transactionIds: `Transactions`, - receiptIds: `Receipts`, - pushTokenIds: `Push Tokens`, - name: `Name`, - redeemedRewards: `Redeemed Rewards`, - id: `id` + primaryKey: { name: `Primary Key`, type: `formula` }, + phoneNumber: { name: `Phone Number`, type: `phone` }, + password: { name: `Password`, type: `text` }, + points: { name: `Points`, type: `number` }, + rewardsAvailable: { name: `Rewards Available`, type: `formula` }, + transactionIds: { name: `Transactions`, type: `foreignKey-many` }, + receiptIds: { name: `Receipts`, type: `foreignKey-many` }, + pushTokenIds: { name: `Push Tokens`, type: `foreignKey-many` }, + rewardsRedeemed: { name: `Rewards Redeemed`, type: `number` }, + id: { name: `id`, type: `formula` }, + name: { name: `Name`, type: `text` } }, 'Push Tokens': { - primaryKey: `Primary Key`, - createdDate: `Created Date`, - customerId: `Customer`, - customerName: `Customer Name`, - token: `Token`, - id: `id` + primaryKey: { name: `Primary Key`, type: `formula` }, + created: { name: `Created`, type: `formula` }, + customerIds: { name: `Customers`, type: `foreignKey-many` }, + customerName: { name: `Customer Name`, type: `lookup` }, + token: { name: `Token`, type: `text` }, + id: { name: `id`, type: `formula` } }, Clerks: { - primaryKey: `Primary Key`, - firstName: `First Name`, - lastName: `Last Name`, - password: `Password`, - transactionIds: `Transactions`, - storeId: `Store`, - clerkName: `Clerk Name`, - id: `id`, - storeName: `Store Name` + primaryKey: { name: `Primary Key`, type: `formula` }, + firstName: { name: `First Name`, type: `text` }, + lastName: { name: `Last Name`, type: `text` }, + password: { name: `Password`, type: `text` }, + transactionIds: { name: `Transactions`, type: `foreignKey-many` }, + storeId: { name: `Store`, type: `foreignKey-one` }, + clerkName: { name: `Clerk Name`, type: `formula` }, + id: { name: `id`, type: `formula` }, + storeName: { name: `Store Name`, type: `lookup` }, + storeId: { name: `Store ID`, type: `lookup` } }, - Transactions: { - primaryKey: `Primary Key`, - customerPhoneNumber: `Customer Phone Number`, - storeId: `Store`, - productsPurchasedId: `Products Purchased`, - customerId: `Customer`, - receiptIds: `Receipts`, - date: `Date`, - pointsRewarded: `Points Rewarded`, - clerkId: `Clerk`, - customerName: `Customer Name`, - lineItems: `Line Items`, - storeName: `Store Name`, - transaction_id: `transaction_id`, - id: `id` + Stores: { + primaryKey: { name: `Primary Key`, type: `formula` }, + ward: { name: `Ward`, type: `text` }, + address: { name: `Address`, type: `text` }, + storeHours: { name: `Store Hours`, type: `text` }, + snapOrEbtAccepted: { name: `SNAP or EBT Accepted`, type: `checkbox` }, + couponProgramPartner: { name: `Coupon Program Partner`, type: `checkbox` }, + transactionIds: { name: `Transactions`, type: `foreignKey-many` }, + latitude: { name: `Latitude`, type: `number` }, + longitude: { name: `Longitude`, type: `number` }, + id: { name: `id`, type: `formula` }, + clerkIds: { name: `Clerks`, type: `foreignKey-many` }, + productIds: { name: `Products`, type: `foreignKey-many` }, + storeName: { name: `Store Name`, type: `text` }, + rewardsAccepted: { name: `Rewards Accepted`, type: `checkbox` }, + clerkFeedbacIds: { name: `Clerk Feedback`, type: `foreignKey-many` } + }, + Products: { + primaryKey: { name: `Primary Key`, type: `formula` }, + category: { name: `Category`, type: `multiSelect` }, + customerCost: { name: `Customer Cost`, type: `number` }, + points: { name: `Points`, type: `formula` }, + multiplier: { name: `Multiplier`, type: `number` }, + id: { name: `id`, type: `formula` }, + lineItemIds: { name: `Line Items`, type: `foreignKey-many` }, + name: { name: `Name`, type: `text` }, + storeIds: { name: `Stores`, type: `foreignKey-many` }, + image: { name: `Image`, type: `multipleAttachment` }, + detail: { name: `Detail`, type: `text` }, + fullName: { name: `Full Name`, type: `formula` }, + fullSizeImage: { name: `Full Size Image`, type: `multipleAttachment` } }, 'Line Items': { - primaryKey: `Primary Key`, - productId: `Product`, - quantity: `Quantity`, - totalPrice: `Total price`, - productPrice: `Product price`, - transactionId: `Transaction`, - productName: `Product Name`, - id: `id` + primaryKey: { name: `Primary Key`, type: `formula` }, + productId: { name: `Product`, type: `foreignKey-one` }, + quantity: { name: `Quantity`, type: `number` }, + totalPrice: { name: `Total price`, type: `formula` }, + productPrice: { name: `Product price`, type: `lookup` }, + transactionId: { name: `Transaction`, type: `foreignKey-one` }, + productFullName: { name: `Product Full Name`, type: `lookup` }, + id: { name: `id`, type: `formula` } }, - Stores: { - primaryKey: `Primary Key`, - ward: `Ward`, - address: `Address`, - storeHours: `Store Hours`, - snapOrEbtAccepted: `SNAP or EBT Accepted`, - couponProgramPartner: `Coupon Program Partner`, - transactionIds: `Transactions`, - latitude: `Latitude`, - longitude: `Longitude`, - id: `id`, - clerkIds: `Clerks`, - productIds: `Products`, - storeName: `Store Name` + Transactions: { + primaryKey: { name: `Primary Key`, type: `formula` }, + storeId: { name: `Store`, type: `foreignKey-one` }, + productsPurchaseIds: { name: `Products Purchased`, type: `foreignKey-many` }, + customerId: { name: `Customer`, type: `foreignKey-one` }, + receiptIds: { name: `Receipts`, type: `foreignKey-many` }, + date: { name: `Date`, type: `formula` }, + pointsEarned: { name: `Points Earned`, type: `number` }, + clerkId: { name: `Clerk`, type: `foreignKey-one` }, + customerName: { name: `Customer Name`, type: `lookup` }, + lineItems: { name: `Line Items`, type: `lookup` }, + storeName: { name: `Store Name`, type: `lookup` }, + id: { name: `id`, type: `formula` }, + rewardsApplied: { name: `Rewards Applied`, type: `number` }, + currentPoints: { name: `Current Points`, type: `number` }, + totalPrice: { name: `Total Price`, type: `number` }, + discount: { name: `Discount`, type: `number` }, + subtotal: { name: `Subtotal`, type: `number` } }, - Products: { - primaryKey: `Primary Key`, - category: `Category`, - customerCost: `Customer Cost`, - points: `Points`, - multiplier: `Multiplier`, - id: `id`, - lineItemIds: `Line Items`, - name: `Name`, - storeIds: `Stores`, - image: `Image` + Recipes: { + primaryKey: { name: `Primary Key`, type: `formula` }, + servings: { name: `Servings`, type: `text` }, + prepTimeminutes: { name: `Prep Time (minutes)`, type: `text` }, + cookTimeminutes: { name: `Cook Time (minutes)`, type: `text` }, + ingredients: { name: `Ingredients`, type: `text` }, + instructions: { name: `Instructions`, type: `text` }, + title: { name: `Title`, type: `text` }, + id: { name: `id`, type: `formula` } + }, + Resources: { + primaryKey: { name: `Primary Key`, type: `formula` }, + url: { name: `URL`, type: `multilineText` }, + description: { name: `Description`, type: `text` }, + category: { name: `Category`, type: `select` }, + title: { name: `Title`, type: `text` }, + id: { name: `id`, type: `formula` } + }, + 'Customer Feedback': { + name: { name: `Name`, type: `text` }, + contact: { name: `Contact`, type: `text` }, + attachments: { name: `Attachments`, type: `multipleAttachment` }, + details: { name: `Details`, type: `multilineText` }, + platform: { name: `Platform`, type: `select` } + }, + 'Clerk Feedback': { + name: { name: `Name`, type: `text` }, + contact: { name: `Contact`, type: `text` }, + attachments: { name: `Attachments`, type: `multipleAttachment` }, + details: { name: `Details`, type: `multilineText` }, + storeNameId: { name: `Store Name`, type: `foreignKey-one` }, + role: { name: `Role`, type: `multiSelect` }, + otherRole: { name: `Other Role`, type: `text` } }, News: { - primaryKey: `Primary Key`, - created: `Created`, - description: `Description`, - id: `id`, - postDate: `Post Date`, - title: `Title` + primaryKey: { name: `Primary Key`, type: `formula` }, + created: { name: `Created`, type: `formula` }, + description: { name: `Description`, type: `multilineText` }, + id: { name: `id`, type: `formula` }, + postDate: { name: `Post Date`, type: `date` }, + title: { name: `Title`, type: `text` } }, Receipts: { - primaryKey: `Primary Key`, - transactionId: `Transaction`, - attachments: `Attachments`, - time: `Time`, - customerId: `Customer`, - customerName: `Customer Name`, - id: `id` - }, - Recipes: { - primaryKey: `Primary Key`, - servings: `Servings`, - prepTimeminutes: `Prep Time (minutes)`, - cookTimeminutes: `Cook Time (minutes)`, - ingredients: `Ingredients`, - instructions: `Instructions`, - title: `Title`, - field8: `Field 8` + primaryKey: { name: `Primary Key`, type: `formula` }, + transactionId: { name: `Transaction`, type: `foreignKey-one` }, + attachments: { name: `Attachments`, type: `multipleAttachment` }, + time: { name: `Time`, type: `date` }, + customerId: { name: `Customer`, type: `foreignKey-one` }, + customerName: { name: `Customer Name`, type: `lookup` }, + id: { name: `id`, type: `formula` } } }; diff --git a/lib/airtable/schemaRaw.json b/lib/airtable/schemaRaw.json deleted file mode 100644 index 30dc54ba..00000000 --- a/lib/airtable/schemaRaw.json +++ /dev/null @@ -1,1328 +0,0 @@ -{ - "tblFjqslig6CAMNR9": { - "id": "tblFjqslig6CAMNR9", - "name": "Customers", - "columns": [ - { - "id": "fldfr5bRHz2NsNTKm", - "name": "Primary Key", - "type": "formula", - "typeOptions": { - "formulaTextParsed": "{column_value_fldSMZKoTpRKjoMyA} & ': ' & {column_value_fld9pYci2JQ0YZIAP} & ' (' & RECORD_ID() & ')'", - "dependencies": { - "referencedColumnIdsForValue": [ - "fldSMZKoTpRKjoMyA", - "fld9pYci2JQ0YZIAP" - ] - }, - "resultType": "text" - }, - "$$hashKey": "object:86" - }, - { - "id": "fldnRoqVKFeE2vzrg", - "name": "First Name", - "type": "multilineText", - "typeOptions": null, - "$$hashKey": "object:87" - }, - { - "id": "fldV89xGbwTgqpBfZ", - "name": "Last Name", - "type": "text", - "typeOptions": null, - "$$hashKey": "object:88" - }, - { - "id": "fldUynpyZEX4qfoHI", - "name": "Zipcode", - "type": "text", - "typeOptions": null, - "$$hashKey": "object:89" - }, - { - "id": "fld9pYci2JQ0YZIAP", - "name": "Phone Number", - "type": "phone", - "typeOptions": null, - "$$hashKey": "object:90" - }, - { - "id": "fld9EYcpFxSHkY9mn", - "name": "Password", - "type": "text", - "typeOptions": null, - "$$hashKey": "object:91" - }, - { - "id": "fldSzDOesWU3at3Vq", - "name": "Points", - "type": "number", - "typeOptions": { - "format": "integer", - "negative": false, - "validatorName": "positive" - }, - "$$hashKey": "object:92" - }, - { - "id": "fldHh1PNTTXZmSNQt", - "name": "Unlocked Rewards", - "type": "formula", - "typeOptions": { - "formulaTextParsed": "column_value_fldSzDOesWU3at3Vq / 500", - "dependencies": { - "referencedColumnIdsForValue": [ - "fldSzDOesWU3at3Vq" - ] - }, - "resultType": "number" - }, - "$$hashKey": "object:93" - }, - { - "id": "fldMnZmFYd3znYXac", - "name": "Transactions", - "type": "foreignKey", - "typeOptions": { - "foreignTableId": "tblqhxusdQjIyddi5", - "symmetricColumnId": "fldiOjXjNhLeqKVAS", - "relationship": "many" - }, - "foreignTable": "tblqhxusdQjIyddi5", - "$$hashKey": "object:94" - }, - { - "id": "fldMZYzBSaxl3Ht33", - "name": "Receipts", - "type": "foreignKey", - "typeOptions": { - "foreignTableId": "tblDb5Sro2qV1JAuz", - "symmetricColumnId": "fldqpi4itmHTmbtEM", - "relationship": "many" - }, - "foreignTable": "tblDb5Sro2qV1JAuz", - "$$hashKey": "object:95" - }, - { - "id": "fldo4bNgsuQrfjxCA", - "name": "Push Tokens", - "type": "foreignKey", - "typeOptions": { - "foreignTableId": "tbljEezc1u6X0wqxd", - "symmetricColumnId": "fld9CtO0aYSLbdeXi", - "relationship": "many" - }, - "foreignTable": "tbljEezc1u6X0wqxd", - "$$hashKey": "object:96" - }, - { - "id": "fldSMZKoTpRKjoMyA", - "name": "Name", - "type": "formula", - "typeOptions": { - "formulaTextParsed": "{column_value_fldnRoqVKFeE2vzrg} & \" \" & {column_value_fldV89xGbwTgqpBfZ}", - "dependencies": { - "referencedColumnIdsForValue": [ - "fldnRoqVKFeE2vzrg", - "fldV89xGbwTgqpBfZ" - ] - }, - "resultType": "text" - }, - "$$hashKey": "object:97" - }, - { - "id": "fldySEvyeTOTHNUO8", - "name": "Redeemed Rewards", - "type": "number", - "typeOptions": { - "format": "integer", - "negative": false, - "validatorName": "positive" - }, - "$$hashKey": "object:98" - }, - { - "id": "fldjJOABSvSXArDGH", - "name": "id", - "type": "formula", - "typeOptions": { - "formulaTextParsed": "RECORD_ID()", - "dependencies": { - "referencedColumnIdsForValue": [] - }, - "resultType": "text" - }, - "$$hashKey": "object:99" - } - ], - "primaryColumnName": "Primary Key", - "defaultView": { - "id": "viwT3Rn6dkbTM4pvG", - "name": "Grid view" - }, - "isEmptyDueToFilter": false, - "isEmpty": false, - "nameForUrl": "customers", - "$$hashKey": "object:3", - "numRecordsToList": 3 - }, - "tbljEezc1u6X0wqxd": { - "id": "tbljEezc1u6X0wqxd", - "name": "Push Tokens", - "columns": [ - { - "id": "fldC6rw2qM1oNGFaf", - "name": "Primary Key", - "type": "formula", - "typeOptions": { - "formulaTextParsed": "'Token: ' & LEFT(RIGHT({column_value_fldugeZksIHKb7Y6P}, LEN({column_value_fldugeZksIHKb7Y6P}) - FIND(\"[\", {column_value_fldugeZksIHKb7Y6P})), LEN({column_value_fldugeZksIHKb7Y6P}) - FIND(\"[\", {column_value_fldugeZksIHKb7Y6P}) - 1) & ' - ' & {column_value_fldp0LSd9Euib57uR} & ' ' & ' (' & RECORD_ID() & ')'", - "dependencies": { - "referencedColumnIdsForValue": [ - "fldugeZksIHKb7Y6P", - "fldp0LSd9Euib57uR" - ] - }, - "resultType": "text" - }, - "$$hashKey": "object:272" - }, - { - "id": "fldiCf6A0E8YYDyGc", - "name": "Created Date", - "type": "formula", - "typeOptions": { - "dateFormat": "ISO", - "isDateTime": true, - "timeFormat": "24hour", - "timeZone": "client", - "displayType": "createdTime", - "formulaTextParsed": "CREATED_TIME()", - "dependencies": { - "referencedColumnIdsForValue": [] - }, - "resultType": "date" - }, - "$$hashKey": "object:273" - }, - { - "id": "fld9CtO0aYSLbdeXi", - "name": "Customer", - "type": "foreignKey", - "typeOptions": { - "relationship": "many", - "foreignTableId": "tblFjqslig6CAMNR9", - "symmetricColumnId": "fldo4bNgsuQrfjxCA" - }, - "foreignTable": "tblFjqslig6CAMNR9", - "$$hashKey": "object:274" - }, - { - "id": "fldp0LSd9Euib57uR", - "name": "Customer Name", - "type": "lookup", - "typeOptions": { - "relationColumnId": "fld9CtO0aYSLbdeXi", - "foreignTableRollupColumnId": "fldSMZKoTpRKjoMyA", - "dependencies": { - "referencedColumnIdsForValue": [ - "fld9CtO0aYSLbdeXi", - "fldSMZKoTpRKjoMyA" - ] - }, - "resultType": "text" - }, - "foreignTableName": "Customers", - "foreignTableRollupColumnName": "Name", - "$$hashKey": "object:275" - }, - { - "id": "fldugeZksIHKb7Y6P", - "name": "Token", - "type": "text", - "typeOptions": null, - "$$hashKey": "object:276" - }, - { - "id": "fldw4e4MvdhyTDJe7", - "name": "id", - "type": "formula", - "typeOptions": { - "formulaTextParsed": "RECORD_ID()", - "dependencies": { - "referencedColumnIdsForValue": [] - }, - "resultType": "text" - }, - "$$hashKey": "object:277" - } - ], - "primaryColumnName": "Primary Key", - "defaultView": { - "id": "viwQZX6bvQpEARTfU", - "name": "Grid view" - }, - "isEmptyDueToFilter": false, - "isEmpty": false, - "nameForUrl": "push%20tokens", - "$$hashKey": "object:4", - "numRecordsToList": 3 - }, - "tbll0b8tyCevgGSk0": { - "id": "tbll0b8tyCevgGSk0", - "name": "Clerks", - "columns": [ - { - "id": "fldPKwlzW1FEQcXLT", - "name": "Primary Key", - "type": "formula", - "typeOptions": { - "formulaTextParsed": "{column_value_fldztcmrfbvdjLhAi} & ' (' & RECORD_ID() & ')'", - "dependencies": { - "referencedColumnIdsForValue": [ - "fldztcmrfbvdjLhAi" - ] - }, - "resultType": "text" - }, - "$$hashKey": "object:380" - }, - { - "id": "fldBx8NOMb8NmRp7j", - "name": "First Name", - "type": "text", - "typeOptions": null, - "$$hashKey": "object:381" - }, - { - "id": "flderd5UqpxVS8t1V", - "name": "Last Name", - "type": "text", - "typeOptions": null, - "$$hashKey": "object:382" - }, - { - "id": "fldIg4neghQ37ZBe1", - "name": "Password", - "type": "text", - "typeOptions": null, - "$$hashKey": "object:383" - }, - { - "id": "fldfhIat6nFU8N9po", - "name": "Transactions", - "type": "foreignKey", - "typeOptions": { - "foreignTableId": "tblqhxusdQjIyddi5", - "symmetricColumnId": "fldjZVXpGywxnr54O", - "relationship": "many" - }, - "foreignTable": "tblqhxusdQjIyddi5", - "$$hashKey": "object:384" - }, - { - "id": "fldOlO1ongfNyQr6f", - "name": "Store", - "type": "foreignKey", - "typeOptions": { - "relationship": "one", - "foreignTableId": "tblRfu3fBhQpUiUlV", - "symmetricColumnId": "fldhRCYqqusFR2hBM" - }, - "foreignTable": "tblRfu3fBhQpUiUlV", - "$$hashKey": "object:385" - }, - { - "id": "fldztcmrfbvdjLhAi", - "name": "Clerk Name", - "type": "text", - "typeOptions": null, - "$$hashKey": "object:386" - }, - { - "id": "fldAmmTQJ0qGJdEEz", - "name": "id", - "type": "formula", - "typeOptions": { - "formulaTextParsed": "RECORD_ID()", - "dependencies": { - "referencedColumnIdsForValue": [] - }, - "resultType": "text" - }, - "$$hashKey": "object:387" - }, - { - "id": "fldZIsxcviRPeJdSB", - "name": "Store Name", - "type": "lookup", - "typeOptions": { - "relationColumnId": "fldOlO1ongfNyQr6f", - "foreignTableRollupColumnId": "fldOGtrT7K4nK1s9R", - "dependencies": { - "referencedColumnIdsForValue": [ - "fldOlO1ongfNyQr6f", - "fldOGtrT7K4nK1s9R" - ] - }, - "resultType": "text" - }, - "foreignTableName": "Stores", - "foreignTableRollupColumnName": "Store Name", - "$$hashKey": "object:388" - } - ], - "primaryColumnName": "Primary Key", - "defaultView": { - "id": "viwZilEUzIIGd6rkh", - "name": "Grid view" - }, - "isEmptyDueToFilter": false, - "isEmpty": false, - "nameForUrl": "clerks", - "$$hashKey": "object:5", - "numRecordsToList": 3 - }, - "tblqhxusdQjIyddi5": { - "id": "tblqhxusdQjIyddi5", - "name": "Transactions", - "columns": [ - { - "id": "fldpP7LO78SYtNS9q", - "name": "Primary Key", - "type": "formula", - "typeOptions": { - "formulaTextParsed": "{column_value_fldJ4uWJQXHdmD6pq} & ' @ ' & {column_value_fldXyTc14Jrvpxmj5} & ' ' & RECORD_ID()", - "dependencies": { - "referencedColumnIdsForValue": [ - "fldJ4uWJQXHdmD6pq", - "fldXyTc14Jrvpxmj5" - ] - }, - "resultType": "text" - }, - "$$hashKey": "object:509" - }, - { - "id": "fldvoVbx0a9PouVUp", - "name": "Customer Phone Number", - "type": "phone", - "typeOptions": null, - "$$hashKey": "object:510" - }, - { - "id": "fldkzTsNrOvnSYUA0", - "name": "Store", - "type": "foreignKey", - "typeOptions": { - "relationship": "one", - "foreignTableId": "tblRfu3fBhQpUiUlV", - "symmetricColumnId": "fldmVXlun5zmOk3ja" - }, - "foreignTable": "tblRfu3fBhQpUiUlV", - "$$hashKey": "object:511" - }, - { - "id": "fldf4jfSaFH0f4xXM", - "name": "Products Purchased", - "type": "foreignKey", - "typeOptions": { - "relationship": "many", - "foreignTableId": "tblpywEfySG9Mqd5N", - "symmetricColumnId": "fldEwWIcq4VZxB4MM" - }, - "foreignTable": "tblpywEfySG9Mqd5N", - "$$hashKey": "object:512" - }, - { - "id": "fldiOjXjNhLeqKVAS", - "name": "Customer", - "type": "foreignKey", - "typeOptions": { - "relationship": "many", - "foreignTableId": "tblFjqslig6CAMNR9", - "symmetricColumnId": "fldMnZmFYd3znYXac" - }, - "foreignTable": "tblFjqslig6CAMNR9", - "$$hashKey": "object:513" - }, - { - "id": "fld3ftifAPGDigjkk", - "name": "Receipts", - "type": "foreignKey", - "typeOptions": { - "foreignTableId": "tblDb5Sro2qV1JAuz", - "symmetricColumnId": "fld1IGW0EkaxJvjoE", - "relationship": "many" - }, - "foreignTable": "tblDb5Sro2qV1JAuz", - "$$hashKey": "object:514" - }, - { - "id": "fld1ppzgfWJRGTBAP", - "name": "Date", - "type": "formula", - "typeOptions": { - "isDateTime": true, - "dateFormat": "Local", - "displayType": "createdTime", - "timeZone": "client", - "timeFormat": "24hour", - "formulaTextParsed": "CREATED_TIME()", - "dependencies": { - "referencedColumnIdsForValue": [] - }, - "resultType": "date" - }, - "$$hashKey": "object:515" - }, - { - "id": "fldzrtcGpZThQMLMS", - "name": "Points Rewarded", - "type": "number", - "typeOptions": { - "format": "integer", - "negative": false, - "validatorName": "positive" - }, - "$$hashKey": "object:516" - }, - { - "id": "fldjZVXpGywxnr54O", - "name": "Clerk", - "type": "foreignKey", - "typeOptions": { - "relationship": "one", - "foreignTableId": "tbll0b8tyCevgGSk0", - "symmetricColumnId": "fldfhIat6nFU8N9po" - }, - "foreignTable": "tbll0b8tyCevgGSk0", - "$$hashKey": "object:517" - }, - { - "id": "fldJ4uWJQXHdmD6pq", - "name": "Customer Name", - "type": "lookup", - "typeOptions": { - "relationColumnId": "fldiOjXjNhLeqKVAS", - "foreignTableRollupColumnId": "fldSMZKoTpRKjoMyA", - "dependencies": { - "referencedColumnIdsForValue": [ - "fldiOjXjNhLeqKVAS", - "fldSMZKoTpRKjoMyA" - ] - }, - "resultType": "text" - }, - "foreignTableName": "Customers", - "foreignTableRollupColumnName": "Name", - "$$hashKey": "object:518" - }, - { - "id": "fld8byhws7WgjTW5Q", - "name": "Line Items", - "type": "lookup", - "typeOptions": { - "relationColumnId": "fldf4jfSaFH0f4xXM", - "foreignTableRollupColumnId": "fldHOLxO57BZ0HFeX", - "dependencies": { - "referencedColumnIdsForValue": [ - "fldf4jfSaFH0f4xXM", - "fldHOLxO57BZ0HFeX" - ] - }, - "resultType": "text" - }, - "foreignTableName": "Line Items", - "foreignTableRollupColumnName": "Product Name", - "$$hashKey": "object:519" - }, - { - "id": "fldXyTc14Jrvpxmj5", - "name": "Store Name", - "type": "lookup", - "typeOptions": { - "relationColumnId": "fldkzTsNrOvnSYUA0", - "foreignTableRollupColumnId": "fldwwQI1hS80qk39Y", - "dependencies": { - "referencedColumnIdsForValue": [ - "fldkzTsNrOvnSYUA0", - "fldwwQI1hS80qk39Y" - ] - }, - "resultType": "text" - }, - "foreignTableName": "Stores", - "foreignTableRollupColumnName": "Primary Key", - "$$hashKey": "object:520" - }, - { - "id": "fldORc4zoU1dtUvIn", - "name": "transaction_id", - "type": "formula", - "typeOptions": { - "formulaTextParsed": "RECORD_ID()", - "dependencies": { - "referencedColumnIdsForValue": [] - }, - "resultType": "text" - }, - "$$hashKey": "object:521" - }, - { - "id": "fldrUpfW5HoToPuf9", - "name": "id", - "type": "formula", - "typeOptions": { - "formulaTextParsed": "RECORD_ID()", - "dependencies": { - "referencedColumnIdsForValue": [] - }, - "resultType": "text" - }, - "$$hashKey": "object:522" - } - ], - "primaryColumnName": "Primary Key", - "defaultView": { - "id": "viwb1P3YRa7nYgQX6", - "name": "Grid view" - }, - "isEmptyDueToFilter": false, - "isEmpty": false, - "nameForUrl": "transactions", - "$$hashKey": "object:6", - "numRecordsToList": 3 - }, - "tblpywEfySG9Mqd5N": { - "id": "tblpywEfySG9Mqd5N", - "name": "Line Items", - "columns": [ - { - "id": "fld28T83u4DaovHmw", - "name": "Primary Key", - "type": "formula", - "typeOptions": { - "formulaTextParsed": "{column_value_fldHOLxO57BZ0HFeX} & ' (' & {column_value_fldzOYAu2Ao6BUquG} & '): ' & {column_value_fldEwWIcq4VZxB4MM}", - "dependencies": { - "referencedColumnIdsForValue": [ - "fldHOLxO57BZ0HFeX", - "fldzOYAu2Ao6BUquG", - "fldEwWIcq4VZxB4MM" - ] - }, - "resultType": "text" - }, - "$$hashKey": "object:687" - }, - { - "id": "fldwKAvbUMszVA6Qo", - "name": "Product", - "type": "foreignKey", - "typeOptions": { - "relationship": "one", - "foreignTableId": "tblA9bNyK9bEXwbj5", - "symmetricColumnId": "fldlK1atTE46O7GCW" - }, - "foreignTable": "tblA9bNyK9bEXwbj5", - "$$hashKey": "object:688" - }, - { - "id": "fldzOYAu2Ao6BUquG", - "name": "Quantity", - "type": "number", - "typeOptions": { - "format": "integer", - "negative": false, - "validatorName": "positive" - }, - "$$hashKey": "object:689" - }, - { - "id": "fldpHfXeiHMsuzM2o", - "name": "Total price", - "type": "formula", - "typeOptions": { - "format": "currency", - "precision": 2, - "symbol": "$", - "formulaTextParsed": "{column_value_fldd7yUSfsgO369nc} * {column_value_fldzOYAu2Ao6BUquG}", - "dependencies": { - "referencedColumnIdsForValue": [ - "fldd7yUSfsgO369nc", - "fldzOYAu2Ao6BUquG" - ] - }, - "resultType": "number" - }, - "$$hashKey": "object:690" - }, - { - "id": "fldd7yUSfsgO369nc", - "name": "Product price", - "type": "lookup", - "typeOptions": { - "relationColumnId": "fldwKAvbUMszVA6Qo", - "foreignTableRollupColumnId": "fld3NqZeBSrbSZkjH", - "format": "currency", - "precision": 2, - "symbol": "$", - "dependencies": { - "referencedColumnIdsForValue": [ - "fldwKAvbUMszVA6Qo", - "fld3NqZeBSrbSZkjH" - ] - }, - "resultType": "number" - }, - "foreignTableName": "Products", - "foreignTableRollupColumnName": "Customer Cost", - "$$hashKey": "object:691" - }, - { - "id": "fldEwWIcq4VZxB4MM", - "name": "Transaction", - "type": "foreignKey", - "typeOptions": { - "foreignTableId": "tblqhxusdQjIyddi5", - "symmetricColumnId": "fldf4jfSaFH0f4xXM", - "relationship": "many" - }, - "foreignTable": "tblqhxusdQjIyddi5", - "$$hashKey": "object:692" - }, - { - "id": "fldHOLxO57BZ0HFeX", - "name": "Product Name", - "type": "lookup", - "typeOptions": { - "relationColumnId": "fldwKAvbUMszVA6Qo", - "foreignTableRollupColumnId": "fldLEt4uLpGkLQS8i", - "dependencies": { - "referencedColumnIdsForValue": [ - "fldwKAvbUMszVA6Qo", - "fldLEt4uLpGkLQS8i" - ] - }, - "resultType": "text" - }, - "foreignTableName": "Products", - "foreignTableRollupColumnName": "Name", - "$$hashKey": "object:693" - }, - { - "id": "fldUCrryhrJr91zAR", - "name": "id", - "type": "formula", - "typeOptions": { - "formulaTextParsed": "RECORD_ID()", - "dependencies": { - "referencedColumnIdsForValue": [] - }, - "resultType": "text" - }, - "$$hashKey": "object:694" - } - ], - "primaryColumnName": "Primary Key", - "defaultView": { - "id": "viw0UpN08Y0QKeA9e", - "name": "Grid view" - }, - "isEmptyDueToFilter": false, - "isEmpty": false, - "nameForUrl": "line%20items", - "$$hashKey": "object:7", - "numRecordsToList": 3 - }, - "tblRfu3fBhQpUiUlV": { - "id": "tblRfu3fBhQpUiUlV", - "name": "Stores", - "columns": [ - { - "id": "fldwwQI1hS80qk39Y", - "name": "Primary Key", - "type": "formula", - "typeOptions": { - "formulaTextParsed": "{column_value_fldOGtrT7K4nK1s9R} & ' @ ' & {column_value_fldTnFWO2d26lHPFR}", - "dependencies": { - "referencedColumnIdsForValue": [ - "fldOGtrT7K4nK1s9R", - "fldTnFWO2d26lHPFR" - ] - }, - "resultType": "text" - }, - "$$hashKey": "object:811" - }, - { - "id": "fldEQ9x8Ef5P4PE7E", - "name": "Ward", - "type": "text", - "typeOptions": null, - "$$hashKey": "object:812" - }, - { - "id": "fldTnFWO2d26lHPFR", - "name": "Address", - "type": "text", - "typeOptions": null, - "$$hashKey": "object:813" - }, - { - "id": "fldCzUeymZKs7nL5D", - "name": "Store Hours", - "type": "text", - "typeOptions": null, - "$$hashKey": "object:814" - }, - { - "id": "fld6t3Juedk03dB72", - "name": "SNAP or EBT Accepted", - "type": "checkbox", - "typeOptions": { - "color": "green", - "icon": "check" - }, - "$$hashKey": "object:815" - }, - { - "id": "fldtZ9JTuz3cVBqUq", - "name": "Coupon Program Partner", - "type": "checkbox", - "typeOptions": { - "color": "green", - "icon": "check" - }, - "$$hashKey": "object:816" - }, - { - "id": "fldmVXlun5zmOk3ja", - "name": "Transactions", - "type": "foreignKey", - "typeOptions": { - "foreignTableId": "tblqhxusdQjIyddi5", - "symmetricColumnId": "fldkzTsNrOvnSYUA0", - "relationship": "many" - }, - "foreignTable": "tblqhxusdQjIyddi5", - "$$hashKey": "object:817" - }, - { - "id": "fldpoqWSqOwVBt0b0", - "name": "Latitude", - "type": "number", - "typeOptions": { - "format": "decimal", - "precision": 6, - "negative": false, - "validatorName": "positive" - }, - "$$hashKey": "object:818" - }, - { - "id": "fldhmSbZuA0dwgGZL", - "name": "Longitude", - "type": "number", - "typeOptions": { - "format": "decimal", - "precision": 6, - "negative": false, - "validatorName": "positive" - }, - "$$hashKey": "object:819" - }, - { - "id": "fldTqWWQdpG1vKdAR", - "name": "id", - "type": "formula", - "typeOptions": { - "formulaTextParsed": "RECORD_ID()", - "dependencies": { - "referencedColumnIdsForValue": [] - }, - "resultType": "text" - }, - "$$hashKey": "object:820" - }, - { - "id": "fldhRCYqqusFR2hBM", - "name": "Clerks", - "type": "foreignKey", - "typeOptions": { - "foreignTableId": "tbll0b8tyCevgGSk0", - "symmetricColumnId": "fldOlO1ongfNyQr6f", - "relationship": "many" - }, - "foreignTable": "tbll0b8tyCevgGSk0", - "$$hashKey": "object:821" - }, - { - "id": "fldyOWJx75QpxaBAf", - "name": "Products", - "type": "foreignKey", - "typeOptions": { - "relationship": "many", - "foreignTableId": "tblA9bNyK9bEXwbj5", - "symmetricColumnId": "fld0jaPCkhJ51Y8F0" - }, - "foreignTable": "tblA9bNyK9bEXwbj5", - "$$hashKey": "object:822" - }, - { - "id": "fldOGtrT7K4nK1s9R", - "name": "Store Name", - "type": "text", - "typeOptions": null, - "$$hashKey": "object:823" - } - ], - "primaryColumnName": "Primary Key", - "defaultView": { - "id": "viw68dujd3AsjEGHl", - "name": "Grid view" - }, - "isEmptyDueToFilter": false, - "isEmpty": false, - "nameForUrl": "stores", - "$$hashKey": "object:8", - "numRecordsToList": 3 - }, - "tblA9bNyK9bEXwbj5": { - "id": "tblA9bNyK9bEXwbj5", - "name": "Products", - "columns": [ - { - "id": "fldGKpjMiplFMEJEq", - "name": "Primary Key", - "type": "formula", - "typeOptions": { - "formulaTextParsed": "{column_value_fldLEt4uLpGkLQS8i} & ' (' & RECORD_ID() & ')'", - "dependencies": { - "referencedColumnIdsForValue": [ - "fldLEt4uLpGkLQS8i" - ] - }, - "resultType": "text" - }, - "$$hashKey": "object:976" - }, - { - "id": "fldKZCR8Go3dblXin", - "name": "Category", - "type": "multiSelect", - "typeOptions": { - "choiceOrder": [ - "selsjwqP8GeIUk8Mb", - "seli6L2szK7CN0B5F", - "selUorZqnIEO0D8pC", - "selBHmzWYKTttHCY0" - ], - "choices": { - "selsjwqP8GeIUk8Mb": { - "id": "selsjwqP8GeIUk8Mb", - "name": "Cut Fruit & Packaged Products", - "color": "red" - }, - "seli6L2szK7CN0B5F": { - "id": "seli6L2szK7CN0B5F", - "name": "Fruit", - "color": "green" - }, - "selUorZqnIEO0D8pC": { - "id": "selUorZqnIEO0D8pC", - "name": "Vegetables", - "color": "teal" - }, - "selBHmzWYKTttHCY0": { - "id": "selBHmzWYKTttHCY0", - "name": "Frozen & Dried", - "color": "purple" - } - }, - "disableColors": false - }, - "$$hashKey": "object:977" - }, - { - "id": "fld3NqZeBSrbSZkjH", - "name": "Customer Cost", - "type": "number", - "typeOptions": { - "format": "decimal", - "precision": 2, - "negative": false, - "validatorName": "positive" - }, - "$$hashKey": "object:978" - }, - { - "id": "fldSPlL0ttNOiq4fc", - "name": "Points", - "type": "formula", - "typeOptions": { - "formulaTextParsed": "IF({column_value_fldhLs3c3IsNowztR}, {column_value_fld3NqZeBSrbSZkjH} * 100 * {column_value_fldhLs3c3IsNowztR}, {column_value_fld3NqZeBSrbSZkjH} * 100)", - "dependencies": { - "referencedColumnIdsForValue": [ - "fldhLs3c3IsNowztR", - "fld3NqZeBSrbSZkjH" - ] - }, - "resultType": "number" - }, - "$$hashKey": "object:979" - }, - { - "id": "fldhLs3c3IsNowztR", - "name": "Multiplier", - "type": "number", - "typeOptions": { - "format": "integer", - "negative": false, - "validatorName": "positive" - }, - "$$hashKey": "object:980" - }, - { - "id": "fldqlCvPpKePufZPi", - "name": "id", - "type": "formula", - "typeOptions": { - "formulaTextParsed": "RECORD_ID()", - "dependencies": { - "referencedColumnIdsForValue": [] - }, - "resultType": "text" - }, - "$$hashKey": "object:981" - }, - { - "id": "fldlK1atTE46O7GCW", - "name": "Line Items", - "type": "foreignKey", - "typeOptions": { - "foreignTableId": "tblpywEfySG9Mqd5N", - "symmetricColumnId": "fldwKAvbUMszVA6Qo", - "relationship": "many" - }, - "foreignTable": "tblpywEfySG9Mqd5N", - "$$hashKey": "object:982" - }, - { - "id": "fldLEt4uLpGkLQS8i", - "name": "Name", - "type": "text", - "typeOptions": null, - "$$hashKey": "object:983" - }, - { - "id": "fld0jaPCkhJ51Y8F0", - "name": "Stores", - "type": "foreignKey", - "typeOptions": { - "foreignTableId": "tblRfu3fBhQpUiUlV", - "symmetricColumnId": "fldyOWJx75QpxaBAf", - "relationship": "many" - }, - "foreignTable": "tblRfu3fBhQpUiUlV", - "$$hashKey": "object:984" - }, - { - "id": "flduYfnJGy2RJT92p", - "name": "Image", - "type": "multipleAttachment", - "typeOptions": null, - "$$hashKey": "object:985" - } - ], - "primaryColumnName": "Primary Key", - "defaultView": { - "id": "viwV51m43oJyZS2fN", - "name": "Grid view" - }, - "isEmptyDueToFilter": false, - "isEmpty": false, - "nameForUrl": "products", - "$$hashKey": "object:9", - "numRecordsToList": 3 - }, - "tbldkXl83HU731E00": { - "id": "tbldkXl83HU731E00", - "name": "News", - "columns": [ - { - "id": "flda9BulN6CFCDXP7", - "name": "Primary Key", - "type": "formula", - "typeOptions": { - "formulaTextParsed": "{column_value_fldqLaND8MIZJ7ce4} & ' (' & RECORD_ID() & ')'", - "dependencies": { - "referencedColumnIdsForValue": [ - "fldqLaND8MIZJ7ce4" - ] - }, - "resultType": "text" - }, - "$$hashKey": "object:1116" - }, - { - "id": "fldxPR6t5RyKScJMO", - "name": "Created", - "type": "formula", - "typeOptions": { - "isDateTime": true, - "dateFormat": "Local", - "displayType": "createdTime", - "timeZone": "client", - "formulaTextParsed": "CREATED_TIME()", - "dependencies": { - "referencedColumnIdsForValue": [] - }, - "resultType": "date" - }, - "$$hashKey": "object:1117" - }, - { - "id": "fldVn8N0XlXsLXVnm", - "name": "Description", - "type": "multilineText", - "typeOptions": null, - "$$hashKey": "object:1118" - }, - { - "id": "fldzoJsC5TXe1Rpvx", - "name": "id", - "type": "formula", - "typeOptions": { - "formulaTextParsed": "RECORD_ID()", - "dependencies": { - "referencedColumnIdsForValue": [] - }, - "resultType": "text" - }, - "$$hashKey": "object:1119" - }, - { - "id": "fldgsU7yErNNoNlYP", - "name": "Post Date", - "type": "date", - "typeOptions": { - "isDateTime": false, - "dateFormat": "Local" - }, - "$$hashKey": "object:1120" - }, - { - "id": "fldqLaND8MIZJ7ce4", - "name": "Title", - "type": "text", - "typeOptions": null, - "$$hashKey": "object:1121" - } - ], - "primaryColumnName": "Primary Key", - "defaultView": { - "id": "viwk7DI5444kHela1", - "name": "Grid view" - }, - "isEmptyDueToFilter": false, - "isEmpty": false, - "nameForUrl": "news", - "$$hashKey": "object:10", - "numRecordsToList": 3 - }, - "tblDb5Sro2qV1JAuz": { - "id": "tblDb5Sro2qV1JAuz", - "name": "Receipts", - "columns": [ - { - "id": "fldHGOdYMxutlEsV9", - "name": "Primary Key", - "type": "formula", - "typeOptions": { - "formulaTextParsed": "\"Receipt - \" & {column_value_fldO5vWAboRn3GgGF} & \" @ \" & {column_value_fldakiVOh6UUmOfTQ} & ' (' & RECORD_ID() & ')'", - "dependencies": { - "referencedColumnIdsForValue": [ - "fldO5vWAboRn3GgGF", - "fldakiVOh6UUmOfTQ" - ] - }, - "resultType": "text" - }, - "$$hashKey": "object:1225" - }, - { - "id": "fld1IGW0EkaxJvjoE", - "name": "Transaction", - "type": "foreignKey", - "typeOptions": { - "relationship": "one", - "foreignTableId": "tblqhxusdQjIyddi5", - "symmetricColumnId": "fld3ftifAPGDigjkk" - }, - "foreignTable": "tblqhxusdQjIyddi5", - "$$hashKey": "object:1226" - }, - { - "id": "fldoQJFu1MvIwV97L", - "name": "Attachments", - "type": "multipleAttachment", - "typeOptions": null, - "$$hashKey": "object:1227" - }, - { - "id": "fldakiVOh6UUmOfTQ", - "name": "Time", - "type": "date", - "typeOptions": { - "isDateTime": true, - "dateFormat": "Local", - "timeFormat": "12hour", - "timeZone": "client" - }, - "$$hashKey": "object:1228" - }, - { - "id": "fldqpi4itmHTmbtEM", - "name": "Customer", - "type": "foreignKey", - "typeOptions": { - "relationship": "one", - "foreignTableId": "tblFjqslig6CAMNR9", - "symmetricColumnId": "fldMZYzBSaxl3Ht33" - }, - "foreignTable": "tblFjqslig6CAMNR9", - "$$hashKey": "object:1229" - }, - { - "id": "fldO5vWAboRn3GgGF", - "name": "Customer Name", - "type": "lookup", - "typeOptions": { - "relationColumnId": "fldqpi4itmHTmbtEM", - "foreignTableRollupColumnId": "fldSMZKoTpRKjoMyA", - "dependencies": { - "referencedColumnIdsForValue": [ - "fldqpi4itmHTmbtEM", - "fldSMZKoTpRKjoMyA" - ] - }, - "resultType": "text" - }, - "foreignTableName": "Customers", - "foreignTableRollupColumnName": "Name", - "$$hashKey": "object:1230" - }, - { - "id": "fldriswVb8VVMSunM", - "name": "id", - "type": "formula", - "typeOptions": { - "formulaTextParsed": "RECORD_ID()", - "dependencies": { - "referencedColumnIdsForValue": [] - }, - "resultType": "text" - }, - "$$hashKey": "object:1231" - } - ], - "primaryColumnName": "Primary Key", - "defaultView": { - "id": "viw9PyDECg0vugnjZ", - "name": "Grid view" - }, - "isEmptyDueToFilter": false, - "isEmpty": false, - "nameForUrl": "receipts", - "$$hashKey": "object:11", - "numRecordsToList": 3 - }, - "tbl3BR6ZlM7nLHZbd": { - "id": "tbl3BR6ZlM7nLHZbd", - "name": "Recipes", - "columns": [ - { - "id": "fldTPaL54oez5VpCt", - "name": "Primary Key", - "type": "formula", - "typeOptions": { - "formulaTextParsed": "{column_value_fld7x0DMp2IcW1GOP} & ' (' & RECORD_ID() & ')'", - "dependencies": { - "referencedColumnIdsForValue": [ - "fld7x0DMp2IcW1GOP" - ] - }, - "resultType": "text" - }, - "$$hashKey": "object:1337" - }, - { - "id": "fldTJ9IEM3a0bQODH", - "name": "Servings", - "type": "text", - "typeOptions": null, - "$$hashKey": "object:1338" - }, - { - "id": "fldYNFZxS9cjSJ8pD", - "name": "Prep Time (minutes)", - "type": "text", - "typeOptions": null, - "$$hashKey": "object:1339" - }, - { - "id": "fldIk0fIjaW1dAy4M", - "name": "Cook Time (minutes)", - "type": "text", - "typeOptions": null, - "$$hashKey": "object:1340" - }, - { - "id": "fld608kBtxQBIS3pY", - "name": "Ingredients", - "type": "text", - "typeOptions": null, - "$$hashKey": "object:1341" - }, - { - "id": "fldB63FqY4K4cUZys", - "name": "Instructions", - "type": "text", - "typeOptions": null, - "$$hashKey": "object:1342" - }, - { - "id": "fld7x0DMp2IcW1GOP", - "name": "Title", - "type": "text", - "typeOptions": null, - "$$hashKey": "object:1343" - }, - { - "id": "fldJgfA7eT0vNWRd5", - "name": "Field 8", - "type": "formula", - "typeOptions": { - "formulaTextParsed": "RECORD_ID()", - "dependencies": { - "referencedColumnIdsForValue": [] - }, - "resultType": "text" - }, - "$$hashKey": "object:1344" - } - ], - "primaryColumnName": "Primary Key", - "defaultView": { - "id": "viw1qGDbYGe3OF0qM", - "name": "Grid view" - }, - "isEmptyDueToFilter": false, - "isEmpty": false, - "nameForUrl": "recipes", - "$$hashKey": "object:12", - "numRecordsToList": 3 - } - } \ No newline at end of file diff --git a/lib/common.js b/lib/common.js index faef97d1..c60799f9 100644 --- a/lib/common.js +++ b/lib/common.js @@ -9,7 +9,7 @@ import Airtable from 'airtable'; // For some reason, will not register the `process.env` variables from `babel-plugin-inline-dotenv` // unless we reference them as variables first -const apiKey = process.env.AIRTABLE_API_KEY; +const apiKey = process.env.REACT_APP_AIRTABLE_API_KEY; const baseId = process.env.AIRTABLE_BASE_ID; const BASE = new Airtable({ diff --git a/package.json b/package.json index ba3caced..89699a8e 100644 --- a/package.json +++ b/package.json @@ -15,8 +15,16 @@ "generate-schema": "generate-airtable-schema" }, "airtable-schema-generator": { - "input": "lib/airtable", - "output": "lib/airtable" + "output": "lib/airtable", + "mode": "auto-headless", + "envFileName": ".env.development", + "schemaMeta": { + "Customers": { + "lookupFields": [ + "Phone Number" + ] + } + } }, "eslintConfig": { "extends": "universe/native" From c128f7769c6d492a897190afd1378ff26f749656 Mon Sep 17 00:00:00 2001 From: "annie.ro98" Date: Wed, 25 Mar 2020 13:28:16 -0700 Subject: [PATCH 06/39] Refactor MapScreen and related components --- components/product/ProductCard.js | 4 +- components/store/StoreCard.js | 47 ++++----------- components/store/StoreProductButton.js | 5 +- lib/mapUtils.js | 80 +++++++------------------- screens/map/MapScreen.js | 37 ++++++------ screens/map/ProductDetailsScreen.js | 2 +- screens/map/StoreListScreen.js | 20 +++---- 7 files changed, 65 insertions(+), 130 deletions(-) diff --git a/components/product/ProductCard.js b/components/product/ProductCard.js index 682ac48b..2c5b1843 100644 --- a/components/product/ProductCard.js +++ b/components/product/ProductCard.js @@ -1,7 +1,7 @@ import React from 'react'; import { Image } from 'react-native'; -import { ButtonContainer, Body } from '../BaseComponents'; import { ColumnContainer } from '../../styled/shared'; +import { Body, ButtonContainer } from '../BaseComponents'; /** * @prop @@ -19,7 +19,7 @@ function ProductCard({ product, store, navigation }) { }> {product.name} diff --git a/components/store/StoreCard.js b/components/store/StoreCard.js index 5e94d4fd..0c873e59 100644 --- a/components/store/StoreCard.js +++ b/components/store/StoreCard.js @@ -1,17 +1,8 @@ import { FontAwesome5 } from '@expo/vector-icons'; import React from 'react'; -import { View } from 'react-native'; import Colors from '../../assets/Colors'; -import { InLineContainer } from '../../styled/shared'; -import { - SpaceAroundRowContainer, - SpaceBetweenRowContainer -} from '../../styled/shared'; -import { - StoreCardContainer, - EBTStatusBar, - DividerBar -} from '../../styled/store'; +import { InLineContainer, SpaceAroundRowContainer, SpaceBetweenRowContainer } from '../../styled/shared'; +import { DividerBar, EBTStatusBar, StoreCardContainer } from '../../styled/store'; import { Body, Caption, Title } from '../BaseComponents'; import StoreProductButton from './StoreProductButton'; @@ -20,13 +11,13 @@ import StoreProductButton from './StoreProductButton'; * */ function StoreCard({ store, callBack, seeProduct }) { - const { name, hours, address, distance, ebt, rewards } = store; + const { storeName, storeHours, address, distance, snapOrEbtAccepted, rewardsAccepted } = store; return ( - {name} - {ebt && ( + {storeName} + {snapOrEbtAccepted && ( EBT @@ -38,35 +29,19 @@ function StoreCard({ store, callBack, seeProduct }) { {distance} miles away - {rewards && ( + {rewardsAccepted && ( - - - Earn and redeem Healthy Rewards here - + + Earn and redeem Healthy Rewards here )} - + {address} - - {hours} + + {storeHours} diff --git a/components/store/StoreProductButton.js b/components/store/StoreProductButton.js index 43d46f71..f4ae9812 100644 --- a/components/store/StoreProductButton.js +++ b/components/store/StoreProductButton.js @@ -1,20 +1,19 @@ import React from 'react'; -import { FilledButtonContainer, ButtonLabel } from '../BaseComponents'; import Colors from '../../assets/Colors'; +import { ButtonLabel, FilledButtonContainer } from '../BaseComponents'; /** * @prop * */ function StoreProductButton({ callBack }) { - // const { name, hours, address, distance, ebt } = store; return ( - See Products + See Products ); } diff --git a/lib/mapUtils.js b/lib/mapUtils.js index 2405ead3..f177a369 100644 --- a/lib/mapUtils.js +++ b/lib/mapUtils.js @@ -1,68 +1,30 @@ -import BASE from './common'; +import { getAllStores, getProductsByIds } from './airtable/request'; -function createStoreData(record) { - const data = record.fields; - return { - name: data['Store Name'], - id: data.id, - latitude: data.Latitude, - longitude: data.Longitude, - hours: data['Store Hours'], - address: data.Address, - products: data.Products, - // distance set and used to sort in MapScreen._orderStoresByDistance - distance: null, - ebt: data['SNAP or EBT Accepted'], - rewards: data['Rewards Accepted'] - }; +// Adds field 'distance' to stores +function updateStoreData(record) { + return { ...record, distance: null }; } -function createProductData(record) { - const data = record.fields; - return { - name: data.Name, - id: data.id, - category: data.Category, - points: data.Points, - customerCost: data['Customer Cost'], - image: data.Image ? data.Image[0].url : null - }; +// Adds field 'imageUrl' to products +function updateProductData(record) { + return { ...record, imageUrl: record.image ? record.image[0].url : null }; } // Gets all records in Airtable from the Stores table // Returns a promise that resolves to an array of Store objects -export const getStoreData = function async() { - return new Promise((resolve, reject) => { - BASE('Stores') - .select() - .all() - .then(records => { - const stores = records.map(record => createStoreData(record)); - resolve(stores); - }) - .catch(err => reject(err)); - }); -}; +export async function getStoreData() { + const records = await getAllStores(); + const stores = records.map(updateStoreData); + return stores; +} // Gets all products for this store using linked records in Store table from Products table -// Returns a promise that resolves to an array of Products objects, -// or null if no products exist for this store -export const getProductData = function async(store) { - // Gracefully handle empty products list in current store - return new Promise((resolve, reject) => { - const currProducts = store.products; - if (currProducts) { - const productRecords = currProducts.map(id => BASE('Products').find(id)); - - Promise.all(productRecords) - .then(records => { - const products = records.map(record => createProductData(record)); - resolve(products); - }) - .catch(err => reject(err)); - } else { - // If no products, resolve with empty array - resolve([]); - } - }); -}; +export async function getProductData(store) { + // Gracefully handle stores without products + if (!('productIds' in store)) { + return []; + } + const records = await getProductsByIds(store.productIds); + const storeProducts = records.map(updateProductData); + return storeProducts; +} diff --git a/screens/map/MapScreen.js b/screens/map/MapScreen.js index f2d9373c..d500e091 100644 --- a/screens/map/MapScreen.js +++ b/screens/map/MapScreen.js @@ -1,9 +1,9 @@ +import { FontAwesome5 } from '@expo/vector-icons'; import * as Location from 'expo-location'; import * as Permissions from 'expo-permissions'; import convertDistance from 'geolib/es/convertDistance'; import getDistance from 'geolib/es/getDistance'; import React from 'react'; -import { FontAwesome5 } from '@expo/vector-icons'; import { Dimensions, SafeAreaView, @@ -13,17 +13,17 @@ import { } from 'react-native'; import MapView, { Marker } from 'react-native-maps'; import BottomSheet from 'reanimated-bottom-sheet'; +import Colors from '../../assets/Colors'; +import { Subhead } from '../../components/BaseComponents'; import Hamburger from '../../components/Hamburger'; import StoreProducts from '../../components/product/StoreProducts'; import { getProductData, getStoreData } from '../../lib/mapUtils'; -import { Subhead } from '../../components/BaseComponents'; import { - SearchBar, BottomSheetContainer, BottomSheetHeaderContainer, - DragBar + DragBar, + SearchBar } from '../../styled/store'; -import Colors from '../../assets/Colors'; const { width } = Dimensions.get('window'); // full width @@ -59,6 +59,7 @@ export default class MapScreen extends React.Component { this._populateInitialStoresProducts(); } + // TODO pretty high chance this should be either handled by navigation or `getDerivedStateFromProps` componentWillReceiveProps(nextProps) { const store = nextProps.navigation.state.params.currentStore; this.changeCurrentStore(store); @@ -91,12 +92,16 @@ export default class MapScreen extends React.Component { // The state is initially populated with stores by calling the Airtable API to get all store records _populateInitialStoresProducts = async () => { - getStoreData() - .then(async stores => { - // If stores exist, we should order them by distance to our current location. - await this._orderStoresByDistance(stores); - }) - .catch(err => console.error(err)); + try { + const stores = await getStoreData(); + // Sets list of stores in state, populates initial products + await this._orderStoresByDistance(stores); + } catch (err) { + console.error( + '[MapScreen] (_populateInitialStoresProducts) Airtable:', + err + ); + } }; _populateStoreProducts = async store => { @@ -106,7 +111,7 @@ export default class MapScreen extends React.Component { this.setState({ storeProducts: products }); } } catch (err) { - console.error(err); + console.error('[MapScreen] (_populateStoreProducts) Airtable:', err); } }; @@ -133,7 +138,7 @@ export default class MapScreen extends React.Component { // Once we choose the closest store, we must populate store products here // Better to perform API calls at top level, and then pass data as props. - await this._populateStoreProducts(stores[0]); + await this._populateStoreProducts(sortedStores[0]); }; renderHeader = () => ( @@ -213,8 +218,8 @@ export default class MapScreen extends React.Component { latitude: store.latitude, longitude: store.longitude }} - title={store.name} - description={store.name} + title={store.storeName} + description={store.storeName} onPress={() => this.changeCurrentStore(store)} /> ))} @@ -258,7 +263,7 @@ export default class MapScreen extends React.Component { }} onPress={() => this.props.navigation.navigate('Rewards')}> - Your Rewards + Your Rewards diff --git a/screens/map/ProductDetailsScreen.js b/screens/map/ProductDetailsScreen.js index 16f30408..f24b0e2f 100644 --- a/screens/map/ProductDetailsScreen.js +++ b/screens/map/ProductDetailsScreen.js @@ -17,7 +17,7 @@ class ProductDetailsScreen extends React.Component { diff --git a/screens/map/StoreListScreen.js b/screens/map/StoreListScreen.js index 381b0e9f..9b0636ab 100644 --- a/screens/map/StoreListScreen.js +++ b/screens/map/StoreListScreen.js @@ -1,17 +1,11 @@ +import { FontAwesome5 } from '@expo/vector-icons'; import React from 'react'; +import { View } from 'react-native'; import { SearchBar } from 'react-native-elements'; // @tommypoa: Create styled-component for this import { ScrollView } from 'react-native-gesture-handler'; -import { View } from 'react-native'; -import { FontAwesome5 } from '@expo/vector-icons'; -import StoreCard from '../../components/store/StoreCard'; - -import { - StoreListContainer, - StoreListHeaderContainer, - StoreListTitle, - styles -} from '../../styled/store'; import Colors from '../../assets/Colors'; +import StoreCard from '../../components/store/StoreCard'; +import { StoreListContainer, StoreListHeaderContainer, StoreListTitle, styles } from '../../styled/store'; class StoreListScreen extends React.Component { constructor(props) { @@ -45,7 +39,7 @@ class StoreListScreen extends React.Component { filterStore(searchStr) { return store => { - return store.name.toLowerCase().includes(searchStr.toLowerCase()); + return store.storeName.toLowerCase().includes(searchStr.toLowerCase()); }; } @@ -63,13 +57,13 @@ class StoreListScreen extends React.Component { value={searchStr} containerStyle={styles.container} inputContainerStyle={styles.inputContainer} - searchIcon={ + searchIcon={( - } + )} inputStyle={styles.input} ref={search => (this.search = search)} /> From d3cf863f44d19a642449b05a5279ab34b2851d57 Mon Sep 17 00:00:00 2001 From: "annie.ro98" Date: Wed, 25 Mar 2020 14:55:43 -0700 Subject: [PATCH 07/39] Refactor News and related components --- components/news/NewsItem.js | 2 +- lib/newsUtils.js | 34 +++++++++++-------------------- screens/news/NewsDetailsScreen.js | 2 +- screens/news/NewsScreen.js | 9 +++++--- 4 files changed, 20 insertions(+), 27 deletions(-) diff --git a/components/news/NewsItem.js b/components/news/NewsItem.js index 0b6e3f41..af2f6ea4 100644 --- a/components/news/NewsItem.js +++ b/components/news/NewsItem.js @@ -29,7 +29,7 @@ class NewsItem extends React.Component { }> - {this.props.newsItem.date.toDateString()} + {this.props.newsItem.postDate.toDateString()} {this.props.newsItem.title} diff --git a/lib/newsUtils.js b/lib/newsUtils.js index 82648550..46578a72 100644 --- a/lib/newsUtils.js +++ b/lib/newsUtils.js @@ -1,27 +1,17 @@ -import BASE from './common'; +import { getAllNews } from './airtable/request'; -function createNewsItemData(newsItem) { - return { - date: new Date(newsItem.get('Created')), - description: newsItem.get('Description'), - title: newsItem.get('Title'), - id: newsItem.id - }; +// Transform postDate to Date object +function updateNewsItemData(record) { + return { ...record, postDate: new Date(record.postDate) }; } -const getNewsItems = function async() { - return BASE('News') - .select({ - view: 'Grid view', - sort: [{ field: 'Created', direction: 'desc' }] - }) - .all() - .then(newsItems => { - const newsItemObjs = newsItems.map(newsItem => - createNewsItemData(newsItem) - ); - return newsItemObjs; - }); -}; +// Gets news items, sorted by 'post date' +export async function getNewsItems() { + const records = await getAllNews('', [ + { field: 'Post Date', direction: 'desc' } + ]); + const newsItems = records.map(updateNewsItemData); + return newsItems; +} export default getNewsItems; diff --git a/screens/news/NewsDetailsScreen.js b/screens/news/NewsDetailsScreen.js index 5a78753a..9f1d6403 100644 --- a/screens/news/NewsDetailsScreen.js +++ b/screens/news/NewsDetailsScreen.js @@ -10,7 +10,7 @@ function NewsDetailsScreen(props) { {currentNewsItem.description} Posted on:  - {currentNewsItem.date.toLocaleDateString()} + {currentNewsItem.postDate.toLocaleDateString()} ); diff --git a/screens/news/NewsScreen.js b/screens/news/NewsScreen.js index d392bacf..3ae0aa6c 100644 --- a/screens/news/NewsScreen.js +++ b/screens/news/NewsScreen.js @@ -2,7 +2,7 @@ import React from 'react'; import { ScrollView, Text, View } from 'react-native'; import Hamburger from '../../components/Hamburger'; import NewsItem from '../../components/news/NewsItem'; -import getNewsItems from '../../lib/newsUtils'; +import { getNewsItems } from '../../lib/newsUtils'; import { TopText } from '../../styled/news'; class NewsScreen extends React.Component { @@ -14,9 +14,12 @@ class NewsScreen extends React.Component { } async componentDidMount() { - getNewsItems().then(newsItems => { + try { + const newsItems = await getNewsItems(); this.setState({ newsItems }); - }); + } catch (err) { + console.error('[NewsScreen] Airtable:', err); + } } render() { From 3915fb2f7d5da15dacb1302116dfd2152d5104b1 Mon Sep 17 00:00:00 2001 From: "annie.ro98" Date: Wed, 25 Mar 2020 15:03:32 -0700 Subject: [PATCH 08/39] Clean up AppNavigator.js --- navigation/AppNavigator.js | 102 ++++------------------------------ navigation/DrawerContent.js | 83 +++++++++++++++++++++++++++ navigation/StackNavigators.js | 27 +++++++-- 3 files changed, 115 insertions(+), 97 deletions(-) create mode 100644 navigation/DrawerContent.js diff --git a/navigation/AppNavigator.js b/navigation/AppNavigator.js index 8b01df7a..f373fd6c 100644 --- a/navigation/AppNavigator.js +++ b/navigation/AppNavigator.js @@ -1,25 +1,19 @@ import React from 'react'; -import { AsyncStorage, View, TouchableOpacity, Linking } from 'react-native'; +import { AsyncStorage } from 'react-native'; import { createAppContainer, createSwitchNavigator } from 'react-navigation'; -import { createDrawerNavigator, DrawerItems } from 'react-navigation-drawer'; -import { createStackNavigator } from 'react-navigation-stack'; -import WelcomeScreen from '../screens/auth/WelcomeScreen'; -import { Title } from '../components/BaseComponents'; -import LoginScreen from '../screens/auth/LoginScreen'; -import SignUpScreen from '../screens/auth/SignUpScreen'; +import { createDrawerNavigator } from 'react-navigation-drawer'; import Colors from '../assets/Colors'; -import { getUser } from '../lib/rewardsUtils'; -import { RewardsStack, StoresStack, ResourcesStack } from './StackNavigators'; - -const AuthStack = createStackNavigator({ - Welcome: WelcomeScreen, - Login: LoginScreen, - SignUp: SignUpScreen -}); +import DrawerContent from './DrawerContent'; +import { + AuthStack, + ResourcesStack, + RewardsStack, + StoresStack +} from './StackNavigators'; class AuthLoadingScreen extends React.Component { - constructor() { - super(); + constructor(props) { + super(props); this._bootstrapAsync(); } @@ -43,80 +37,6 @@ class AuthLoadingScreen extends React.Component { } } -export class DrawerContent extends React.Component { - constructor() { - super(); - this.state = { - user: { - id: null, - name: null - }, - link: 'http://tiny.cc/RewardsFeedback' - }; - } - async componentDidMount() { - const userId = await AsyncStorage.getItem('userId'); - getUser(userId).then(userRecord => { - if (userRecord) { - const user = { - id: userId, - name: userRecord.fields.Name - }; - this.setState({ user }); - return true; - } - return false; - }); - } - - _logout = async () => { - AsyncStorage.clear(); - this.props.navigation.navigate('Auth'); - }; - - render() { - return ( - - - {this.state.user.name} - - - - Linking.openURL(this.state.link)}> - Report Issue - - this._logout()}> - Logout - - - - ); - } -} - const MyDrawerNavigator = createDrawerNavigator( { Stores: { diff --git a/navigation/DrawerContent.js b/navigation/DrawerContent.js new file mode 100644 index 00000000..5507e6b8 --- /dev/null +++ b/navigation/DrawerContent.js @@ -0,0 +1,83 @@ +import React from 'react'; +import { AsyncStorage, Linking, TouchableOpacity, View } from 'react-native'; +import { DrawerItems } from 'react-navigation-drawer'; +import Colors from '../assets/Colors'; +import { Title } from '../components/BaseComponents'; +import { getUser } from '../lib/rewardsUtils'; + +class DrawerContent extends React.Component { + constructor() { + super(); + this.state = { + user: { + id: null, + name: null + }, + link: 'http://tiny.cc/RewardsFeedback' + }; + } + + async componentDidMount() { + const userId = await AsyncStorage.getItem('userId'); + getUser(userId).then(userRecord => { + if (userRecord) { + const user = { + id: userId, + name: userRecord.fields.Name + }; + this.setState({ user }); + return true; + } + return false; + }); + } + + _logout = async () => { + AsyncStorage.clear(); + this.props.navigation.navigate('Auth'); + }; + + render() { + return ( + + + {this.state.user.name} + + + + Linking.openURL(this.state.link)}> + Report Issue + + this._logout()}> + Logout + + + + ); + } +} + +export default DrawerContent; diff --git a/navigation/StackNavigators.js b/navigation/StackNavigators.js index 48ca453f..cc3b6b92 100644 --- a/navigation/StackNavigators.js +++ b/navigation/StackNavigators.js @@ -1,22 +1,37 @@ -import { createStackNavigator } from 'react-navigation-stack'; import { Platform } from 'react-native'; - +import { createStackNavigator } from 'react-navigation-stack'; +// Auth +import LoginScreen from '../screens/auth/LoginScreen'; +import SignUpScreen from '../screens/auth/SignUpScreen'; +import WelcomeScreen from '../screens/auth/WelcomeScreen'; +// Map import MapScreen from '../screens/map/MapScreen'; -import RewardsScreen from '../screens/rewards/RewardsScreen'; -import ReceiptScanner from '../screens/rewards/Camera'; - import ProductDetailsScreen from '../screens/map/ProductDetailsScreen'; import ProductsScreen from '../screens/map/ProductsScreen'; import StoreListScreen from '../screens/map/StoreListScreen'; -import NewsScreen from '../screens/news/NewsScreen'; +// News import NewsDetailsScreen from '../screens/news/NewsDetailsScreen'; +import NewsScreen from '../screens/news/NewsScreen'; +// Resources import ResourcesScreen from '../screens/resources/ResourcesScreen'; +// Rewards +import ReceiptScanner from '../screens/rewards/Camera'; +import RewardsScreen from '../screens/rewards/RewardsScreen'; const config = Platform.select({ web: { headerMode: 'screen' }, default: {} }); +export const AuthStack = createStackNavigator( + { + Welcome: WelcomeScreen, + Login: LoginScreen, + SignUp: SignUpScreen + }, + config +); + export const StoresStack = createStackNavigator( { Stores: MapScreen, From 1f57b240dd7005ef0640d5cb744c26598570bad0 Mon Sep 17 00:00:00 2001 From: "annie.ro98" Date: Wed, 25 Mar 2020 15:03:47 -0700 Subject: [PATCH 09/39] Refactor Resources --- lib/resourceUtils.js | 25 ------------- screens/resources/ResourcesScreen.js | 54 ++++++++++++++-------------- 2 files changed, 28 insertions(+), 51 deletions(-) delete mode 100644 lib/resourceUtils.js diff --git a/lib/resourceUtils.js b/lib/resourceUtils.js deleted file mode 100644 index ad86b5fb..00000000 --- a/lib/resourceUtils.js +++ /dev/null @@ -1,25 +0,0 @@ -import BASE from './common'; - -function createResourceData(resource) { - return { - url: resource.get('URL'), - description: resource.get('Description'), - title: resource.get('Title'), - category: resource.get('Category'), - id: resource.id - }; -} - -const getResources = function async() { - return BASE('Resources') - .select() - .all() - .then(resources => { - const resourceObjs = resources.map(resource => - createResourceData(resource) - ); - return resourceObjs; - }); -}; - -export default getResources; diff --git a/screens/resources/ResourcesScreen.js b/screens/resources/ResourcesScreen.js index 97cf3f23..e7832920 100644 --- a/screens/resources/ResourcesScreen.js +++ b/screens/resources/ResourcesScreen.js @@ -1,17 +1,16 @@ +import { FontAwesome5 } from '@expo/vector-icons'; import React from 'react'; -import { TopText } from '../../styled/resources'; -import { TouchableOpacity, ScrollView, View, Button } from 'react-native'; -import getResources from '../../lib/resourceUtils'; +import { ScrollView, View } from 'react-native'; +import { NavButton } from '../../components/BaseComponents'; import ResourceCard from '../../components/resources/ResourceCard'; -import { Title, NavButton } from '../../components/BaseComponents'; import ResourceCategoryBar from '../../components/resources/ResourceCategoryBar'; -import { FontAwesome5 } from '@expo/vector-icons'; +import { getAllResources } from '../../lib/airtable/request'; +import { TopText } from '../../styled/resources'; class ResourcesScreen extends React.Component { constructor(props) { super(props); this.state = { - resources: [], DCCentralKitchenResources: [], CommunityResources: [], GovernmentResources: [], @@ -20,26 +19,29 @@ class ResourcesScreen extends React.Component { } async componentDidMount() { - getResources().then(resources => { - (DCCentralKitchenResources = resources.filter( + try { + const resources = await getAllResources(); + const DCCentralKitchenResources = resources.filter( resource => resource.category == 'DC Central Kitchen Resources' - )), - (CommunityResources = resources.filter( - resource => resource.category == 'Community Resources' - )), - (GovernmentResources = resources.filter( - resource => resource.category == 'Government Resources' - )), - (ResourcesForSeniors = resources.filter( - resource => resource.category == 'Resources for Seniors' - )), - this.setState({ - DCCentralKitchenResources, - CommunityResources, - GovernmentResources, - ResourcesForSeniors - }); - }); + ); + const CommunityResources = resources.filter( + resource => resource.category == 'Community Resources' + ); + const GovernmentResources = resources.filter( + resource => resource.category == 'Government Resources' + ); + const ResourcesForSeniors = resources.filter( + resource => resource.category == 'Resources for Seniors' + ); + this.setState({ + DCCentralKitchenResources, + CommunityResources, + GovernmentResources, + ResourcesForSeniors + }); + } catch (err) { + console.error('[ResourcesScreen] Airtable: ', err); + } } render() { @@ -85,7 +87,7 @@ class ResourcesScreen extends React.Component { navigation={this.props.navigation} /> ))} - + ); From f24205d6293b674effc7a513c91ca87afd4d28bb Mon Sep 17 00:00:00 2001 From: "annie.ro98" Date: Wed, 25 Mar 2020 15:18:50 -0700 Subject: [PATCH 10/39] Replace getUser --- navigation/DrawerContent.js | 38 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/navigation/DrawerContent.js b/navigation/DrawerContent.js index 5507e6b8..6a5492ed 100644 --- a/navigation/DrawerContent.js +++ b/navigation/DrawerContent.js @@ -3,33 +3,26 @@ import { AsyncStorage, Linking, TouchableOpacity, View } from 'react-native'; import { DrawerItems } from 'react-navigation-drawer'; import Colors from '../assets/Colors'; import { Title } from '../components/BaseComponents'; -import { getUser } from '../lib/rewardsUtils'; +import { getCustomersById } from '../lib/airtable/request'; class DrawerContent extends React.Component { - constructor() { - super(); + constructor(props) { + super(props); this.state = { - user: { - id: null, - name: null - }, - link: 'http://tiny.cc/RewardsFeedback' + customer: null, + link: 'http://tiny.cc/RewardsFeedback', + isLoading: true }; } async componentDidMount() { - const userId = await AsyncStorage.getItem('userId'); - getUser(userId).then(userRecord => { - if (userRecord) { - const user = { - id: userId, - name: userRecord.fields.Name - }; - this.setState({ user }); - return true; - } - return false; - }); + try { + const customerId = await AsyncStorage.getItem('userId'); + const customer = await getCustomersById(customerId); + this.setState({ customer, isLoading: false }); + } catch (err) { + console.error('[DrawerContent] Airtable:', err); + } } _logout = async () => { @@ -38,6 +31,9 @@ class DrawerContent extends React.Component { }; render() { + if (this.state.isLoading) { + return null; + } return ( - {this.state.user.name} + {this.state.customer.name} Date: Wed, 25 Mar 2020 16:04:20 -0700 Subject: [PATCH 11/39] Refactor Rewards and related components. - Deprecated: removed pull-to-refresh implementation to declutter the file. We can add it back from a different trigger if necessary. Removed deprecated props which were not being used in `RewardsHome` and `PointsHistory` components. - Cleanup: Made `Overline` components normally-cased (since the styled component uses `text-transform`) - Cleanup: Added `isLoading` to `DrawerContent` and `RewardsScreen`. This is probably the most crucial place to add a loading screen (@ace) imo; it's noticeably laggy here. --- components/rewards/PointsHistory.js | 48 +++++------- components/rewards/RewardsHome.js | 36 ++++----- constants/Rewards.js | 0 lib/rewardsUtils.js | 48 +++--------- screens/rewards/RewardsScreen.js | 114 +++++----------------------- 5 files changed, 69 insertions(+), 177 deletions(-) create mode 100644 constants/Rewards.js diff --git a/components/rewards/PointsHistory.js b/components/rewards/PointsHistory.js index 3cd9d0a4..443efbef 100644 --- a/components/rewards/PointsHistory.js +++ b/components/rewards/PointsHistory.js @@ -1,42 +1,32 @@ import React from 'react'; -import { Button, ScrollView, StyleSheet, Text, View } from 'react-native'; +import { ScrollView, View } from 'react-native'; import { Overline } from '../BaseComponents'; import Transaction from './Transaction'; + /** * @prop * */ -const styles = StyleSheet.create({ - signOutButton: { - fontSize: 20, - paddingVertical: 15 - } -}); - -function PointsHistory({ transactions, user, updates, navigation }) { - // Only display if transactions have mounted +function PointsHistory({ transactions }) { // TODO @kennethlien fix spacing at line 44 - if (transactions) { - return ( + + return ( + - - - Recent Transactions - {transactions.map(transaction => ( - - ))} - - + + Recent Transactions + {transactions.map(transaction => ( + + ))} + - ); - } - // else return - return ; + + ); } export default React.memo(PointsHistory); diff --git a/components/rewards/RewardsHome.js b/components/rewards/RewardsHome.js index 4146186d..b34026df 100644 --- a/components/rewards/RewardsHome.js +++ b/components/rewards/RewardsHome.js @@ -1,53 +1,53 @@ import React from 'react'; -import { View, ScrollView } from 'react-native'; -import { Body, Overline, Title } from '../BaseComponents'; +import { ScrollView, View } from 'react-native'; +import { ProgressBar } from 'react-native-paper'; +import Colors from '../../assets/Colors'; import { - RewardsProgressContainer, - AvailiableRewardsContainer + AvailiableRewardsContainer, + RewardsProgressContainer } from '../../styled/rewards'; -import { ProgressBar } from 'react-native-paper'; +import { Body, Overline, Title } from '../BaseComponents'; import RewardsCard from './RewardsCard'; -import Colors from '../../assets/Colors'; /** * @prop * */ -function createList(N) { - let list = []; - for (var i = 1; i <= N; i++) { +function createList(n) { + const list = []; + for (let i = 1; i <= n; i += 1) { list.push(i); } return list; } -function RewardsHome({ user }) { +function RewardsHome({ customer }) { return ( - REWARD PROGRESS + Reward Progress - {parseInt(user.points) % 1000} / 1000 + {parseInt(customer.points) % 1000} / 1000 - Earn {`${1000 - (parseInt(user.points) % 1000)}`} points to unlock - your next $5 reward + Earn {`${1000 - (parseInt(customer.points) % 1000)}`} points to + unlock your next $5 reward - AVAILIABLE REWARDS ({Math.floor(parseInt(user.points) / 1000)}) + Available Rewards ({Math.floor(parseInt(customer.points) / 1000)}) - {createList(Math.floor(parseInt(user.points) / 1000)).map(a => ( - + {createList(Math.floor(parseInt(customer.points) / 1000)).map(i => ( + ))} diff --git a/constants/Rewards.js b/constants/Rewards.js new file mode 100644 index 00000000..e69de29b diff --git a/lib/rewardsUtils.js b/lib/rewardsUtils.js index 282de18f..47227596 100644 --- a/lib/rewardsUtils.js +++ b/lib/rewardsUtils.js @@ -1,42 +1,16 @@ -import BASE from './common'; +import { getAllTransactions } from './airtable/request'; -function createTransactionData(record) { - const transaction = record.fields; - return { - id: transaction.id, - customer: transaction.Customer, - // TODO not entirely sure why this was converted to a Date object - date: new Date(transaction.Date), - points: transaction['Points Rewarded'], - storeName: transaction['Store Name'], - productsPurchased: transaction['Products Purchased'], - phone: transaction['Customer Lookup (Phone #)'], - clerk: transaction.Clerk, - itemsRedeemed: transaction['Items Redeemed'], - customerName: transaction['Customer Name'], - receipts: transaction.Receipts - }; +// Transform date to Date object +function updateTransactionData(record) { + return { ...record, date: new Date(record.date) }; } -// Calls Airtable API to return a promise that -// will resolve to be a user record. -function getUser(id) { - return BASE('Customers').find(id); +export async function getCustomerTransactions(userId) { + const transactions = await getAllTransactions( + `SEARCH("${userId}", {Customer})`, + [{ field: 'Date', direction: 'desc' }] + ); + return transactions.map(updateTransactionData); } -const getCustomerTransactions = function async(userId) { - return BASE('Transactions') - .select({ - filterByFormula: `SEARCH("${userId}", {Customer})`, - sort: [{ field: 'Date', direction: 'desc' }] - }) - .all() - .then(transactions => { - const transObjs = transactions.map(transaction => - createTransactionData(transaction) - ); - return transObjs; - }); -}; - -export { getCustomerTransactions, getUser }; +export default getCustomerTransactions; diff --git a/screens/rewards/RewardsScreen.js b/screens/rewards/RewardsScreen.js index 1d3577d7..797d4999 100644 --- a/screens/rewards/RewardsScreen.js +++ b/screens/rewards/RewardsScreen.js @@ -5,7 +5,8 @@ import { TabBar, TabView } from 'react-native-tab-view'; import { Title } from '../../components/BaseComponents'; import PointsHistory from '../../components/rewards/PointsHistory'; import RewardsHome from '../../components/rewards/RewardsHome'; -import { getCustomerTransactions, getUser } from '../../lib/rewardsUtils'; +import { getCustomersById } from '../../lib/airtable/request'; +import { getCustomerTransactions } from '../../lib/rewardsUtils'; import { BackButton, Container, styles, TopTab } from '../../styled/rewards'; const routes = [ @@ -17,115 +18,38 @@ export default class RewardsScreen extends React.Component { constructor(props) { super(props); this.state = { - user: { - id: null, - points: null, - name: null - }, + customer: null, transactions: [], - refreshing: false, - updates: false, index: 0, - routes + routes, + isLoading: true }; } - // Load user record & transactions + // Load customer record & transactions async componentDidMount() { - const userId = await AsyncStorage.getItem('userId'); - getUser(userId) - .then(userRecord => { - if (userRecord) { - const user = { - id: userId, - points: userRecord.fields.Points, - name: userRecord.fields.Name - }; - this.setState({ user }); - return true; - } - console.error('User data is undefined or empty.'); - return false; - }) - .then(exists => { - if (exists) { - // Get transactions - getCustomerTransactions(userId).then(transactions => { - this.setState({ latestTransaction: transactions[0], transactions }); - }); - } - }) - .catch(err => { - console.error(err); - }); - } + const customerId = await AsyncStorage.getItem('userId'); + const customer = await getCustomersById(customerId); - // This is what runs when you pull to refresh - (what runs in componentDidMount plus some modifications) - // It updates the current transactions & user points, - // sets 'update' to true if the latest transaction is new - // or if the latest transaction does not have a receipt attached - // which prompts on the History page - _onRefresh = async () => { - this.setState({ refreshing: true }); - const userId = await AsyncStorage.getItem('userId'); - getUser(userId) - .then(userRecord => { - if (userRecord) { - const user = { - id: userId, - points: userRecord.fields.Points, - name: userRecord.fields.Name - }; - this.setState({ user }); - return true; - } - console.error('User data is undefined or empty.'); - return false; - }) - .then(exists => { - if (exists) { - getCustomerTransactions(this.state.user.id).then(transactions => { - if (this.state.latestTransaction !== transactions[0]) { - this.setState({ - latestTransaction: transactions[0], - transactions - }); - } - if (this.state.latestTransaction.receipts == null) { - this.setState({ updates: true }); - } - this.setState({ refreshing: false }); - }); - } - }) - .catch(err => console.error(err)); - }; + const transactions = await getCustomerTransactions(customerId); + this.setState({ + customer, + transactions, + isLoading: false + }); + } renderScene = ({ route }) => { switch (route.key) { case 'home': - return ( - - ); + return ; case 'history': - return ( - - ); + return ; default: return null; } }; - // TODO refactor to use style-components; jank af right now - // TODO use Poppins for the font renderTabBar = props => { return ( From c79e52de8100764b8590d31e4a0c3d3aa16b4d2d Mon Sep 17 00:00:00 2001 From: "annie.ro98" Date: Wed, 25 Mar 2020 16:41:22 -0700 Subject: [PATCH 12/39] Use constants/Rewards.js for reward point value --- components/rewards/RewardsHome.js | 26 +++++++++----------------- constants/Rewards.js | 3 +++ 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/components/rewards/RewardsHome.js b/components/rewards/RewardsHome.js index b34026df..e7eadb5e 100644 --- a/components/rewards/RewardsHome.js +++ b/components/rewards/RewardsHome.js @@ -2,10 +2,7 @@ import React from 'react'; import { ScrollView, View } from 'react-native'; import { ProgressBar } from 'react-native-paper'; import Colors from '../../assets/Colors'; -import { - AvailiableRewardsContainer, - RewardsProgressContainer -} from '../../styled/rewards'; +import { AvailiableRewardsContainer, RewardsProgressContainer } from '../../styled/rewards'; import { Body, Overline, Title } from '../BaseComponents'; import RewardsCard from './RewardsCard'; @@ -22,31 +19,26 @@ function createList(n) { } function RewardsHome({ customer }) { + const rewardsAvailable = parseInt(customer.points, 10) / rewardPointValue; + const pointsToNext = parseInt(customer.points) % rewardPointValue; return ( - - Reward Progress - - - {parseInt(customer.points) % 1000} / 1000 - + Reward Progress + {`${pointsToNext} / ${rewardPointValue}`} - Earn {`${1000 - (parseInt(customer.points) % 1000)}`} points to - unlock your next $5 reward + Earn {`${rewardPointValue - pointsToNext}`} points to unlock your next $5 reward - - Available Rewards ({Math.floor(parseInt(customer.points) / 1000)}) - + Available Rewards ({Math.floor(rewardsAvailable)} - {createList(Math.floor(parseInt(customer.points) / 1000)).map(i => ( + {createList(Math.floor(rewardsAvailable)).map(i => ( ))} diff --git a/constants/Rewards.js b/constants/Rewards.js index e69de29b..c92880f5 100644 --- a/constants/Rewards.js +++ b/constants/Rewards.js @@ -0,0 +1,3 @@ +export const rewardDollarValue = 5; + +export const rewardPointValue = 1500; From ae7b2621b39427eb31d66bfd081bb2078bf04e46 Mon Sep 17 00:00:00 2001 From: Tommy Poa Date: Thu, 26 Mar 2020 10:48:22 +0800 Subject: [PATCH 13/39] Error working with both signup and login. Styling not done. --- components/AuthTextField.js | 28 ++++--------- screens/auth/LoginScreen.js | 69 +++++++++++++++--------------- screens/auth/SignUpScreen.js | 81 ++++++++++++++++-------------------- styled/auth.js | 7 ---- 4 files changed, 78 insertions(+), 107 deletions(-) diff --git a/components/AuthTextField.js b/components/AuthTextField.js index c2123dea..bb15fef8 100644 --- a/components/AuthTextField.js +++ b/components/AuthTextField.js @@ -1,8 +1,7 @@ import React from 'react'; import { TextField } from 'react-native-materialui-textfield'; import Colors from '../assets/Colors'; -import { fieldStateColors } from '../lib/authUtils'; -import { TextFieldContainer, InputNoticeContainer } from '../styled/auth'; +import { TextFieldContainer } from '../styled/auth'; import { Caption } from './BaseComponents'; /** @@ -11,26 +10,23 @@ import { Caption } from './BaseComponents'; function AuthTextField({ fieldType, - color, value, onBlurCallback, - onFocusCallback, changeTextCallback, error, errorMessage }) { return ( - {/* {fieldType} */} onBlurCallback(value) : null} autoCapitalize={'words'} autoCorrect={false} label={fieldType} labelTextStyle={{ fontFamily: 'poppins-regular' }} onChangeText={changeTextCallback} value={value} + baseColor={Colors.activeText} tintColor={Colors.primaryGreen} error={error === '' ? '' : errorMessage} errorColor={Colors.darkerOrange} @@ -39,19 +35,11 @@ function AuthTextField({ maxLength={fieldType === 'Phone Number' ? 10 : null} secureTextEntry={fieldType === 'Password'} /> - - {fieldType === 'Name' && ( - - Note: this is how clerks will greet you! - - )} - {color === fieldStateColors.ERROR && fieldType === 'Phone Number' && ( - Must be a valid phone number - )} - {color === fieldStateColors.ERROR && fieldType === 'Password' && ( - Must be 8-20 characters long - )} - + {fieldType === 'Name' && ( + + Note: this is how clerks will greet you! + + )} ); } diff --git a/screens/auth/LoginScreen.js b/screens/auth/LoginScreen.js index 3fac2b35..dc6de1a5 100644 --- a/screens/auth/LoginScreen.js +++ b/screens/auth/LoginScreen.js @@ -2,19 +2,16 @@ import { Notifications } from 'expo'; import Constants from 'expo-constants'; import * as Permissions from 'expo-permissions'; import React from 'react'; -import { AsyncStorage, Button } from 'react-native'; -import { - signUpFields, - fieldStateColors, - lookupCustomer, - updateCustomerPushTokens -} from '../../lib/authUtils'; +import { AsyncStorage } from 'react-native'; +import { lookupCustomer, updateCustomerPushTokens } from '../../lib/authUtils'; import { ErrorMsg, AuthScreenContainer, FormContainer } from '../../styled/auth'; import { JustifyCenterContainer } from '../../styled/shared'; +import Colors from '../../assets/Colors'; + import { BigTitle, ButtonLabel, @@ -31,10 +28,7 @@ export default class Login extends React.Component { password: '', errorMsg: '', token: null, - indicators: { - [signUpFields.PHONENUM]: [fieldStateColors.INACTIVE], - [signUpFields.PASSWORD]: [fieldStateColors.INACTIVE] - } + loginPermission: false }; } @@ -78,6 +72,17 @@ export default class Login extends React.Component { } }; + handleLoginPermission() { + let loginPermission; + const { phoneNumber, password } = this.state; + if (phoneNumber.length == 10 && password.length >= 8) { + loginPermission = true; + } else { + loginPermission = false; + } + this.setState({ loginPermission }); + } + // This function will reformat the phone number to (XXX) XXX-XXXX and sign the user in if // the user is found. handleSubmit() { @@ -127,22 +132,6 @@ export default class Login extends React.Component { .catch(err => console.error(err)); } - onFocus(signUpField) { - const { indicators } = this.state; - indicators[signUpField] = fieldStateColors.FOCUSED; - this.setState({ - indicators - }); - } - - onBlur(signUpField) { - const { indicators } = this.state; - indicators[signUpField] = fieldStateColors.BLURRED; - this.setState({ - indicators - }); - } - render() { return ( @@ -150,26 +139,34 @@ export default class Login extends React.Component { this.onBlur(signUpFields.PHONENUM)} - onFocusCallback={() => this.onFocus(signUpFields.PHONENUM)} - changeTextCallback={text => this.setState({ phoneNumber: text })} + changeTextCallback={text => { + this.handleLoginPermission(); + this.setState({ phoneNumber: text }); + }} + onBlurCallback={() => this.handleLoginPermission()} /> this.onBlur(signUpFields.PASSWORD)} - onFocusCallback={() => this.onFocus(signUpFields.PASSWORD)} - changeTextCallback={text => this.setState({ password: text })} + changeTextCallback={text => { + this.handleLoginPermission(); + this.setState({ password: text }); + }} + onBlurCallback={() => this.handleLoginPermission()} /> this.handleSubmit()}> + onPress={() => this.handleSubmit()} + disabled={!this.state.loginPermission}> Log in diff --git a/screens/auth/SignUpScreen.js b/screens/auth/SignUpScreen.js index d3662de7..39f87f10 100644 --- a/screens/auth/SignUpScreen.js +++ b/screens/auth/SignUpScreen.js @@ -6,7 +6,6 @@ import { AsyncStorage, Button, Keyboard } from 'react-native'; import validatejs from 'validate.js'; import { signUpFields, - fieldStateColors, checkForDuplicateCustomer, createCustomer, createPushToken @@ -35,11 +34,6 @@ export default class SignUp extends React.Component { phoneNumber: '', phoneNumberError: '', token: '', - indicators: { - [signUpFields.NAME]: [fieldStateColors.INACTIVE], - [signUpFields.PHONENUM]: [fieldStateColors.INACTIVE], - [signUpFields.PASSWORD]: [fieldStateColors.INACTIVE] - }, signUpPermission: false }; } @@ -236,30 +230,18 @@ export default class SignUp extends React.Component { Keyboard.dismiss(); } - onFocus(signUpField) { - const { indicators } = this.state; - if (indicators[signUpField] == fieldStateColors.ERROR) { - return; - } else { - indicators[signUpField] = fieldStateColors.FOCUSED; - this.setState({ - indicators - }); - } - } - - onBlur(signUpField) { - const { indicators } = this.state; - indicators[signUpField] = this.handleErrorState(signUpField); - this.updateErrors(); - this.setState({ - indicators - }); + onBlur(text, signUpField) { + this.updateError(text, signUpField); } // Typing can only remove errors, not trigger them updateError(text, signUpField) { - let { nameError, passwordError, phoneNumberError } = this.state; + let { + nameError, + passwordError, + phoneNumberError, + signUpPermission + } = this.state; switch (signUpField) { case signUpFields.NAME: if (!text.replace(/\s/g, '').length) { @@ -277,10 +259,16 @@ export default class SignUp extends React.Component { default: break; } + if (!nameError && !passwordError && !phoneNumberError) { + signUpPermission = true; + } else { + signUpPermission = false; + } this.setState({ nameError, passwordError, - phoneNumberError + phoneNumberError, + signUpPermission }); } @@ -292,12 +280,14 @@ export default class SignUp extends React.Component { this.onBlur(signUpFields.NAME)} - onFocusCallback={() => this.onFocus(signUpFields.NAME)} + onBlurCallback={value => + this.updateError(value, signUpFields.NAME) + } changeTextCallback={text => { - this.updateError(text, signUpFields.NAME); + if (this.state.nameError) { + this.updateError(text, signUpFields.NAME); + } this.setState({ name: text }); }} error={this.state.nameError} @@ -306,12 +296,14 @@ export default class SignUp extends React.Component { this.onBlur(signUpFields.PHONENUM)} - onFocusCallback={() => this.onFocus(signUpFields.PHONENUM)} + onBlurCallback={value => + this.updateError(value, signUpFields.PHONENUM) + } changeTextCallback={text => { - this.updateError(text, signUpFields.PHONENUM); + if (this.state.phoneNumberError) { + this.updateError(text, signUpFields.PHONENUM); + } this.setState({ phoneNumber: text }); }} error={this.state.phoneNumberError} @@ -320,12 +312,14 @@ export default class SignUp extends React.Component { this.onBlur(signUpFields.PASSWORD)} - onFocusCallback={() => this.onFocus(signUpFields.PASSWORD)} + onBlurCallback={value => + this.updateError(value, signUpFields.PASSWORD) + } changeTextCallback={text => { - this.updateError(text, signUpFields.PASSWORD); + if (this.state.passwordError) { + this.updateError(text, signUpFields.PASSWORD); + } this.setState({ password: text }); }} error={this.state.passwordError} @@ -392,12 +386,11 @@ const validation = { length: { is: 10, message: '^Please enter a valid phone number.' + }, + // To check for only numbers + numericality: { + onlyInteger: true } - // To check for only numbers in the future - // format: { - // pattern: "/^\d+$/", - // message: "Phone number cannot contain nondigits." - // } }, password: { diff --git a/styled/auth.js b/styled/auth.js index c6746077..b4e7fe76 100644 --- a/styled/auth.js +++ b/styled/auth.js @@ -63,10 +63,3 @@ export const TextField = styled.TextInput` border-bottom-color: ${props => props.borderColor || Colors.activeText}; border-bottom-width: 2px; `; - -// SignUpScreen - -// TODO @tommypoa: consider adding props to margins to reduce number of containers -export const InputNoticeContainer = styled.View` - margin-top: 4px; -`; From 33e0f7a6647de8a2371b32b29dd3988ffc4d6f64 Mon Sep 17 00:00:00 2001 From: "annie.ro98" Date: Wed, 25 Mar 2020 20:12:55 -0700 Subject: [PATCH 14/39] Overhaul Signup --- components/AuthTextField.js | 34 +-- lib/authUtils.js | 134 ++++++----- screens/auth/SignUpScreen.js | 449 +++++++++++++---------------------- 3 files changed, 250 insertions(+), 367 deletions(-) diff --git a/components/AuthTextField.js b/components/AuthTextField.js index 0ad21afd..eaecbbda 100644 --- a/components/AuthTextField.js +++ b/components/AuthTextField.js @@ -1,54 +1,40 @@ import React from 'react'; import Colors from '../assets/Colors'; import { fieldStateColors } from '../lib/authUtils'; -import { - InputNoticeContainer, - TextField, - TextFieldContainer -} from '../styled/auth'; +import { InputNoticeContainer, TextField, TextFieldContainer } from '../styled/auth'; import { Caption } from './BaseComponents'; /** * @prop * */ -function AuthTextField({ - fieldType, - color, - value, - onBlurCallback, - onFocusCallback, - changeTextCallback -}) { +function AuthTextField({ fieldType, errorMsg, color, value, onBlurCallback, onFocusCallback, changeTextCallback }) { return ( {fieldType} - {fieldType === 'Name' && ( - - Note: this is how clerks will greet you! - + {/* Name */} + {color !== fieldStateColors.ERROR && fieldType === 'Name' && ( + Note: this is how clerks will greet you! )} - {color === fieldStateColors.ERROR && fieldType === 'Phone Number' && ( - Must be a valid phone number - )} - {color === fieldStateColors.ERROR && fieldType === 'Password' && ( + {color === fieldStateColors.ERROR && {errorMsg}} + {/* {color === fieldStateColors.ERROR && fieldType === 'Password' && ( Must be 8-20 characters long - )} + )} */} ); diff --git a/lib/authUtils.js b/lib/authUtils.js index b0a6d320..d6908076 100644 --- a/lib/authUtils.js +++ b/lib/authUtils.js @@ -1,5 +1,6 @@ -import BASE from './common'; +import validatejs from 'validate.js'; import Colors from '../assets/Colors'; +import BASE from './common'; // Fields @@ -12,58 +13,6 @@ export const fieldStateColors = { ERROR: Colors.darkerOrange }; -// Takes in Expo-generated token (a string) -// and creates a new record in the Push Tokens table -export const createPushToken = function async(token) { - return BASE('Push Tokens').create([ - { - fields: { - Token: token - } - } - ]); -}; - -// Creates a customer record in Airtable -export const createCustomer = function async( - name, - phoneNumber, - password, - tokenId -) { - return BASE('Customers').create([ - { - fields: { - Name: name, - 'Phone Number': phoneNumber, - Password: password, - Points: 0, - 'Push Tokens': [tokenId] - } - } - ]); -}; - -// Used in SignUpScreen. Checks if phone number is already in use. -export const checkForDuplicateCustomer = function async(phoneNumber) { - return new Promise((resolve, reject) => { - BASE('Customers') - .select({ - maxRecords: 1, - filterByFormula: `SEARCH("${phoneNumber}", {Phone Number})` - }) - .firstPage() - .then(records => { - if (records.length > 0) { - resolve(true); - } else { - resolve(false); - } - }) - .catch(err => reject(err)); - }); -}; - // lookupCustomer searches for users based on their phone numbers // in the form (XXX) XXX-XXXX and checks against a correct password // If the user is found, we return a subset of fields in Airtable @@ -100,10 +49,7 @@ export const lookupCustomer = function async(phoneNumber, password) { }; // updateCustomerPushTokens is called after lookupCustomer; and only if lookupCustomer returns non-null. -export const updateCustomerPushTokens = function async( - customerInfo, - currentToken -) { +export const updateCustomerPushTokens = function async(customerInfo, currentToken) { // currentToken is the value generated by Expo; a string return new Promise((resolve, reject) => { const { custId, tokenNames } = customerInfo; @@ -172,3 +118,77 @@ export const updateCustomerPushTokens = function async( } }); }; + +export function formatPhoneNumber(phoneNumber) { + const onlyNumeric = phoneNumber.replace('[^0-9]', ''); + const formatted = `(${onlyNumeric.slice(0, 3)}) ${onlyNumeric.slice(3, 6)}-${onlyNumeric.slice(6, 10)}`; + return formatted; +} + +// For future use, to match for better passwords +// TODO: @Johnathan Fix passwords check +const pattern = '((?=.*d)(?=.*[a-z])(?=.*[A-Z])(?=.*[W]).{6,20})'; + +// This is to create constraints for the validatejs library +const validation = { + name: { + presence: { + message: '^Name cannot be blank.' + } + }, + phoneNumber: { + // This verifies that it's not blank. + presence: { + message: '^Phone number cannot be blank.' + }, + length: { + is: 10, + message: '^Must be a valid phone number' + } + // To check for only numbers in the future + // format: { + // pattern: '/^d+$/', + // message: 'Phone number cannot contain non-numeric characters.' + // } + }, + + password: { + presence: { + message: '^Password cannot be blank.' + }, + length: { + minimum: 8, + maximum: 20, + message: '^Must be 8-20 characters long' + } + // For future use for better password checking + // format: { + // pattern: "[a-z0-9]+", + // flags: "i", + // message: "Must contain at least one digit, one lowercase number, and special chracter" + // } + } +}; + +// This is the validate function that utilizes validate.js +// to check a fieldname based on an inputted value. +export function validate(fieldName, value) { + // Validate.js validates your values as an object + // e.g. var form = {email: 'email@example.com'} + // Line 8-9 creates an object based on the field name and field value + const values = {}; + values[fieldName] = value; + + const constraints = {}; + constraints[fieldName] = validation[fieldName]; + // The values and validated against the constraints + // the variable result hold the error messages of the field + const result = validatejs(values, constraints); + // If there is an error message, return it! + if (result) { + // Return only the field error message if there are multiple + return result[fieldName][0]; + } + // Otherwise, return null + return null; +} diff --git a/screens/auth/SignUpScreen.js b/screens/auth/SignUpScreen.js index 2736b545..22c50efe 100644 --- a/screens/auth/SignUpScreen.js +++ b/screens/auth/SignUpScreen.js @@ -3,43 +3,38 @@ import Constants from 'expo-constants'; import * as Permissions from 'expo-permissions'; import React from 'react'; import { AsyncStorage, Button, Keyboard } from 'react-native'; -import validatejs from 'validate.js'; -import { - signUpFields, - fieldStateColors, - checkForDuplicateCustomer, - createCustomer, - createPushToken -} from '../../lib/authUtils'; - +import { ScrollView } from 'react-native-gesture-handler'; import Colors from '../../assets/Colors'; - -import { - BigTitle, - ButtonLabel, - FilledButtonContainer -} from '../../components/BaseComponents'; import AuthTextField from '../../components/AuthTextField'; -import { FormContainer, AuthScreenContainer } from '../../styled/auth'; -import { ScrollView } from 'react-native-gesture-handler'; +import { BigTitle, ButtonLabel, FilledButtonContainer } from '../../components/BaseComponents'; +import { createCustomers, createPushTokens, getCustomersByPhoneNumber } from '../../lib/airtable/request'; +import { fieldStateColors, formatPhoneNumber, signUpFields, validate } from '../../lib/authUtils'; +import { AuthScreenContainer, FormContainer } from '../../styled/auth'; export default class SignUp extends React.Component { constructor(props) { super(props); this.state = { - name: '', - nameError: '', - password: '', - passwordError: '', - phoneNumber: '', - phoneNumberError: '', - token: '', + values: { + [signUpFields.NAME]: '', + [signUpFields.PHONENUM]: '', + [signUpFields.PASSWORD]: '' + }, + + errors: { + [signUpFields.NAME]: 'placeholder', + [signUpFields.PHONENUM]: 'placeholder', + [signUpFields.PASSWORD]: 'placeholder', + // Duplicate phone number - not being used currently + submit: '' + }, indicators: { - [signUpFields.NAME]: [fieldStateColors.INACTIVE], - [signUpFields.PHONENUM]: [fieldStateColors.INACTIVE], - [signUpFields.PASSWORD]: [fieldStateColors.INACTIVE] + [signUpFields.NAME]: fieldStateColors.INACTIVE, + [signUpFields.PHONENUM]: fieldStateColors.INACTIVE, + [signUpFields.PASSWORD]: fieldStateColors.INACTIVE }, + token: '', signUpPermission: false }; } @@ -48,17 +43,31 @@ export default class SignUp extends React.Component { // Notifications is jank - the `_handleNotification` function doesn't even exist. Unclear to devs what the flow should be with receiving notifications componentDidMount() { // this.registerForPushNotificationsAsync(); - - // Handle notifications that are received or selected while the app - // is open. If the app was closed and then opened by tapping the - // notification (rather than just tapping the app icon to open it), - // this function will fire on the next tick after the app starts - // with the notification data. - this._notificationSubscription = Notifications.addListener( - this._handleNotification - ); + // this._notificationSubscription = Notifications.addListener(this._handleNotification); } + _clearState = () => { + this.setState({ + values: { + [signUpFields.NAME]: '', + [signUpFields.PHONENUM]: '', + [signUpFields.PASSWORD]: '' + }, + errors: { + [signUpFields.NAME]: 'placeholder', + [signUpFields.PHONENUM]: 'placeholder', + [signUpFields.PASSWORD]: 'placeholder' + }, + indicators: { + [signUpFields.NAME]: fieldStateColors.INACTIVE, + [signUpFields.PHONENUM]: fieldStateColors.INACTIVE, + [signUpFields.PASSWORD]: fieldStateColors.INACTIVE + }, + token: '', + signUpPermission: false + }); + }; + // Purely to bypass signups for development -- developer is not required to sign up to enter home screen. // Configures to use David Ro's test account _devBypass = async () => { @@ -69,214 +78,149 @@ export default class SignUp extends React.Component { // Sign in function. It sets the user token in local storage // to be the fname + lname and then navigates to homescreen. - _asyncSignin = async () => { - await AsyncStorage.setItem('userId', this.state.id); + _asyncSignin = async customerId => { + await AsyncStorage.setItem('userId', customerId); this.props.navigation.navigate('App'); }; registerForPushNotificationsAsync = async () => { if (Constants.isDevice) { - const { status: existingStatus } = await Permissions.getAsync( - Permissions.NOTIFICATIONS - ); + const { status: existingStatus } = await Permissions.getAsync(Permissions.NOTIFICATIONS); let finalStatus = existingStatus; if (existingStatus !== 'granted') { - const { status } = await Permissions.askAsync( - Permissions.NOTIFICATIONS - ); + const { status } = await Permissions.askAsync(Permissions.NOTIFICATIONS); finalStatus = status; } if (finalStatus !== 'granted') { alert('Failed to get push token for push notification!'); return; } - const pushToken = await Notifications.getExpoPushTokenAsync(); - await this.setState({ token: pushToken }); + const token = await Notifications.getExpoPushTokenAsync(); + await this.setState({ token }); } else { alert('Must use physical device for Push Notifications'); } }; - // Helper function for adding customers to the database. Takes - // in all the relevant information from the form and calls the - // Airtable API to create the record. - async addCustomer() { - return new Promise((resolve, reject) => { - createPushToken(this.state.token) - .then(tokenRecords => { - if (tokenRecords) { - let tokenId = null; - - // Get tokenId - tokenRecords.forEach(function process(record) { - // Resolve with tokenId for use in other functions - tokenId = record.getId(); - }); - - // Create customer with this tokenId - createCustomer( - this.state.name, - this.state.phoneNumber, - this.state.password, - tokenId - ) - .then(customerRecords => { - if (customerRecords) { - let custId = null; - - customerRecords.forEach(function id(record) { - custId = record.getId(); - }); - - resolve(custId); - return custId; - } - reject(new Error('Error creating new customer')); + // Update sign up permission based on whether there are any errors left + updatePermission = async () => { + const { errors } = this.state; + const signUpPermission = + !errors[signUpFields.NAME] && !errors[signUpFields.PHONENUM] && !errors[signUpFields.PASSWORD]; - // double error handling? - }) - .catch(err => { - console.error(err); - reject(err); - }); - } else { - // No tokenRecord found - reject(new Error('Error creating new push token')); - } - }) - .catch(err => { - console.error(err); - reject(err); - }); + this.setState({ + signUpPermission }); - } - updateErrors = async () => { - const phoneNumberError = validate('phoneNumber', this.state.phoneNumber); - const passwordError = validate('password', this.state.password); - let nameError = ''; - if (!this.state.name) { - nameError = 'Name inputs cannot be blank.'; - } - // In case we want to do name checking using validate.js - // const nameError = validate('name', this.state.name) + return signUpPermission; + }; - let formattedPhoneNumber = this.state.phoneNumber; - // eslint-disable-next-line prettier/prettier - formattedPhoneNumber = `(${formattedPhoneNumber.slice( - 0, - 3 - )}) ${formattedPhoneNumber.slice(3, 6)}-${formattedPhoneNumber.slice( - 6, - 10 - )}`; - // Update sign up permission based on whether there are any errors left - let signUpPermission = false; - if (!phoneNumberError && !passwordError && !nameError) { - signUpPermission = true; + // Add customer to Airtable, creating a push token record first. + addCustomer = async () => { + const { token, values } = this.state; + const name = values[signUpFields.NAME]; + const phoneNumber = values[signUpFields.PHONENUM]; + const password = values[signUpFields.PASSWORD]; + try { + const pushTokenId = await createPushTokens({ token }); + const customerId = await createCustomers({ name, phoneNumber, password, points: 0, pushTokenIds: [pushTokenId] }); + return customerId; + } catch (err) { + console.error('[SignUpScreen] (addCustomer) Airtable:', err); } - - // Have to await this or else Airtable call may happen first - await this.setState({ - nameError, - phoneNumberError, - passwordError, - signUpPermission - }); - return formattedPhoneNumber; }; - // Handling submission. This function runs the validation functions - // as well as the duplicate checking. If there are no errors on the - // validation or duplicate side, then an Airtable record is created. - async handleSubmit() { - const formattedPhoneNumber = this.updateErrors(); - // If we don't have any bugs already with form validation, - // we'll check for duplicates here in the Airtable. - if (!this.state.phoneNumberError) { - const that = this; - await checkForDuplicateCustomer(formattedPhoneNumber).then( - async resolvedValue => { - if (resolvedValue) { - // Again, must await this - await that.setState({ - phoneNumberError: 'Phone number in use already.' - }); - } + // Handle form submission. Validate fields first, then check duplicates. + // If there are no errors, add customer to Airtable. + handleSubmit = async () => { + const signUpPermission = await this.updatePermission(); + if (signUpPermission) { + try { + // Check for duplicates first + const formattedPhoneNumber = formatPhoneNumber(this.state.values[signUpFields.PHONENUM]); + const duplicateCustomers = await getCustomersByPhoneNumber(formattedPhoneNumber); + if (duplicateCustomers.length !== 0) { + console.log('Duplicate customer'); + await this.setState(prevState => ({ + errors: { ...prevState.errors, submit: 'Phone number already in use.' } + })); + return; } - ); - } - - if ( - !this.state.nameError && - !this.state.phoneNumberError && - !this.state.passwordError - ) { - await this.addCustomer() - .then(custId => { - this.setState({ - name: '', - password: '', - passwordError: '', - phoneNumber: '', - phoneNumberError: '', - token: '', - id: custId - }); - }) - .catch(err => { - console.error(err); - }) - .then(_ => this._asyncSignin()); + // Otherwise, add customer to Airtable + const customerId = await this.addCustomer(); + this._clearState(); + await this._asyncSignin(customerId); + } catch (err) { + console.error('[SignUpScreen] (handleSubmit) Airtable:', err); + } } else { - alert( - `${this.state.nameError}\n ${this.state.phoneNumberError}\n ${this.state.passwordError}` - ); + console.log('errors'); + // alert(`${this.state.nameError}\n ${this.state.phoneNumberError}\n ${this.state.passwordError}`); } Keyboard.dismiss(); - } + }; - handleErrorState(signUpField) { - if ( - signUpField == signUpFields.PHONENUM && - validate('phoneNumber', this.state.phoneNumber) - ) { - return fieldStateColors.ERROR; - } else if ( - signUpField == signUpFields.PASSWORD && - validate('password', this.state.password) - ) { - return fieldStateColors.ERROR; - } else if ( - signUpField == signUpFields.NAME && - !this.state.name.replace(/\s/g, '').length - ) { + handleErrorState = async signUpField => { + let error = false; + let errorMsg = ''; + const fieldValue = this.state.values[signUpField]; + // validate returns null if no error is found + switch (signUpField) { + case signUpFields.PHONENUM: + errorMsg = validate('phoneNumber', fieldValue); + error = errorMsg !== null; + break; + case signUpFields.PASSWORD: + errorMsg = validate('password', fieldValue); + error = errorMsg !== null; + break; + case signUpFields.NAME: + error = !fieldValue.replace(/\s/g, '').length; + if (error) errorMsg = 'Name cannot be blank'; + break; + default: + console.log('Not reached'); + } + if (error) { + this.setState(prevState => ({ + errors: { ...prevState.errors, [signUpField]: errorMsg } + })); return fieldStateColors.ERROR; - } else { - return fieldStateColors.BLURRED; } - } + return fieldStateColors.BLURRED; + }; - onFocus(signUpField) { + onFocus = async signUpField => { const { indicators } = this.state; - if (indicators[signUpField] == fieldStateColors.ERROR) { - return; - } else { - indicators[signUpField] = fieldStateColors.FOCUSED; - this.setState({ - indicators - }); + if (indicators[signUpField] !== fieldStateColors.ERROR) { + await this.setState(prevState => ({ + indicators: { ...prevState.indicators, [signUpField]: fieldStateColors.FOCUSED } + })); } - } + }; - onBlur(signUpField) { - const { indicators } = this.state; - indicators[signUpField] = this.handleErrorState(signUpField); - this.updateErrors(); - this.setState({ - indicators - }); - } + // onBlur callback is required in case customer taps on field, does nothing, and taps out + onBlur = async signUpField => { + const updatedIndicator = await this.handleErrorState(signUpField); + this.setState(prevState => ({ + indicators: { ...prevState.indicators, [signUpField]: updatedIndicator } + })); + await this.updatePermission(); + }; + + onTextChange = async (signUpField, text) => { + await this.setState(prevState => ({ + values: { ...prevState.values, [signUpField]: text } + })); + // Set updated value before error-checking + const updatedIndicator = await this.handleErrorState(signUpField); + const errorFound = updatedIndicator === fieldStateColors.ERROR; + this.setState(prevState => ({ + indicators: { ...prevState.indicators, [signUpField]: updatedIndicator }, + errors: { ...prevState.errors, [signUpField]: errorFound ? prevState.errors[signUpField] : '' } + })); + await this.updatePermission(); + }; render() { return ( @@ -286,42 +230,41 @@ export default class SignUp extends React.Component { this.onBlur(signUpFields.NAME)} onFocusCallback={() => this.onFocus(signUpFields.NAME)} - changeTextCallback={text => this.setState({ name: text })} + changeTextCallback={text => this.onTextChange(signUpFields.NAME, text)} /> this.onBlur(signUpFields.PHONENUM)} onFocusCallback={() => this.onFocus(signUpFields.PHONENUM)} - changeTextCallback={text => this.setState({ phoneNumber: text })} + changeTextCallback={text => this.onTextChange(signUpFields.PHONENUM, text)} /> this.onBlur(signUpFields.PASSWORD)} onFocusCallback={() => this.onFocus(signUpFields.PASSWORD)} - changeTextCallback={text => this.setState({ password: text })} + changeTextCallback={text => this.onTextChange(signUpFields.PASSWORD, text)} /> this.handleSubmit()} disabled={!this.state.signUpPermission}> - SIGN UP + Sign Up