From 166a07ae023428c3962e5b75c0328438e24734d6 Mon Sep 17 00:00:00 2001 From: alex-cannon Date: Wed, 20 Nov 2019 12:47:56 -0700 Subject: [PATCH 01/19] chore: installed react-redux, redux-thunk --- package.json | 2 ++ yarn.lock | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/package.json b/package.json index 443b2c1..f109bb4 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,9 @@ "qs": "^6.7.0", "react": "^16.8.6", "react-dom": "^16.8.6", + "react-redux": "^7.1.3", "react-router-dom": "^5.0.0", + "redux-thunk": "^2.3.0", "styled-components": "^4.2.0", "styled-normalize": "^8.0.6", "ts-jest": "^24.1.0" diff --git a/yarn.lock b/yarn.lock index 0bf78fd..4dfd38f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -885,6 +885,13 @@ dependencies: regenerator-runtime "^0.13.2" +"@babel/runtime@^7.5.5": + version "7.7.2" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.7.2.tgz#111a78002a5c25fc8e3361bedc9529c696b85a6a" + integrity sha512-JONRbXbTXc9WQE2mAZd1p0Z3DZ/6vaQIkgYMSTP3KjRCyd7rCZCcfhCyX+YjwcKxcZ82UrxbRD358bpExNgrjw== + dependencies: + regenerator-runtime "^0.13.2" + "@babel/template@^7.0.0", "@babel/template@^7.1.0", "@babel/template@^7.2.2", "@babel/template@^7.4.0": version "7.4.0" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.4.0.tgz#12474e9c077bae585c5d835a95c0b0b790c25c8b" @@ -3810,6 +3817,13 @@ hoist-non-react-statics@^3.1.0: dependencies: react-is "^16.7.0" +hoist-non-react-statics@^3.3.0: + version "3.3.1" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#101685d3aff3b23ea213163f6e8e12f4f111e19f" + integrity sha512-wbg3bpgA/ZqWrZuMOeJi8+SKMhr7X9TesL/rXMjTzh0p0JUBo3II8DHboYbuIXWRlttrUFxwcu/5kygrCw8fJw== + dependencies: + react-is "^16.7.0" + hosted-git-info@^2.1.4: version "2.7.1" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.7.1.tgz#97f236977bd6e125408930ff6de3eec6281ec047" @@ -6660,6 +6674,23 @@ react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.6.tgz#5bbc1e2d29141c9fbdfed456343fe2bc430a6a16" integrity sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA== +react-is@^16.9.0: + version "16.12.0" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.12.0.tgz#2cc0fe0fba742d97fd527c42a13bec4eeb06241c" + integrity sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q== + +react-redux@^7.1.3: + version "7.1.3" + resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.1.3.tgz#717a3d7bbe3a1b2d535c94885ce04cdc5a33fc79" + integrity sha512-uI1wca+ECG9RoVkWQFF4jDMqmaw0/qnvaSvOoL/GA4dNxf6LoV8sUAcNDvE5NWKs4hFpn0t6wswNQnY3f7HT3w== + dependencies: + "@babel/runtime" "^7.5.5" + hoist-non-react-statics "^3.3.0" + invariant "^2.2.4" + loose-envify "^1.4.0" + prop-types "^15.7.2" + react-is "^16.9.0" + react-router-dom@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.0.0.tgz#542a9b86af269a37f0b87218c4c25ea8dcf0c073" @@ -6764,6 +6795,11 @@ realpath-native@^1.1.0: dependencies: util.promisify "^1.0.0" +redux-thunk@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622" + integrity sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw== + regenerate-unicode-properties@^8.0.2: version "8.0.2" resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.0.2.tgz#7b38faa296252376d363558cfbda90c9ce709662" From 085976b7184f5729928a75bbeb6a37af3e5800ed Mon Sep 17 00:00:00 2001 From: alex-cannon Date: Wed, 20 Nov 2019 13:18:49 -0700 Subject: [PATCH 02/19] chore: hacked temp reducer so it'll work --- package.json | 3 ++- src/core/store.ts | 13 +++++++++++++ src/{index.jsx => index.tsx} | 12 +++++++++++- tsconfig.json | 13 +++++++++++++ yarn.lock | 10 +++++++++- 5 files changed, 48 insertions(+), 3 deletions(-) create mode 100644 src/core/store.ts rename src/{index.jsx => index.tsx} (87%) create mode 100644 tsconfig.json diff --git a/package.json b/package.json index f109bb4..7e66b0d 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "fd-servicedirectory", "version": "1.0.0", "description": "PFA Service Directory", - "main": "src/index.jsx", + "main": "src/index.tsx", "repository": "git@github.com:CodeForFoco/fd-servicedirectory.git", "author": "Code for Fort Collins", "license": "MIT", @@ -58,6 +58,7 @@ "react-dom": "^16.8.6", "react-redux": "^7.1.3", "react-router-dom": "^5.0.0", + "redux": "^4.0.4", "redux-thunk": "^2.3.0", "styled-components": "^4.2.0", "styled-normalize": "^8.0.6", diff --git a/src/core/store.ts b/src/core/store.ts new file mode 100644 index 0000000..de127c2 --- /dev/null +++ b/src/core/store.ts @@ -0,0 +1,13 @@ +import { createStore, applyMiddleware } from "redux"; +import thunk from "redux-thunk"; + +// Temporary Reducer. Remove this. +const defaultReducer = (state, action) => { + let newAction = action; + Object.keys(newAction); + return state; +}; + +export const configureStore = initialState => { + return createStore(defaultReducer, initialState, applyMiddleware(thunk)); +}; diff --git a/src/index.jsx b/src/index.tsx similarity index 87% rename from src/index.jsx rename to src/index.tsx index 14b243e..8c141ff 100644 --- a/src/index.jsx +++ b/src/index.tsx @@ -1,8 +1,10 @@ import React from "react"; import { render } from "react-dom"; import { BrowserRouter, Redirect, Route, Switch } from "react-router-dom"; +import { Provider } from "react-redux"; import styled, { createGlobalStyle, ThemeProvider } from "styled-components"; import { Normalize } from "styled-normalize"; +import { configureStore } from "~/core/store"; import Nav from "~/components/nav"; import theme from "~/core/theme"; import Categories from "~/pages/categories"; @@ -23,6 +25,9 @@ const PageContainer = styled.div({ marginBottom: "96px", }); +// Initialize Redux store +const store = configureStore({}); + const App = () => ( @@ -52,4 +57,9 @@ const App = () => ( ); -render(, document.getElementById("app")); +render( + + + , + document.getElementById("app") +); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..487708a --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,13 @@ +// References: +// https://parceljs.org/typeScript.html +// https://www.typescriptlang.org/docs/handbook/tsconfig-json.html +{ + "compilerOptions": { + "baseUrl": "./src", + "paths": { + "~*": ["./*"] + }, + "jsx": "react", + }, + "include": ["src/**/*"] +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 4dfd38f..d53fbf9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6800,6 +6800,14 @@ redux-thunk@^2.3.0: resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622" integrity sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw== +redux@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.4.tgz#4ee1aeb164b63d6a1bcc57ae4aa0b6e6fa7a3796" + integrity sha512-vKv4WdiJxOWKxK0yRoaK3Y4pxxB0ilzVx6dszU2W8wLxlb2yikRph4iV/ymtdJ6ZxpBLFbyrxklnT5yBbQSl3Q== + dependencies: + loose-envify "^1.4.0" + symbol-observable "^1.2.0" + regenerate-unicode-properties@^8.0.2: version "8.0.2" resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.0.2.tgz#7b38faa296252376d363558cfbda90c9ce709662" @@ -7704,7 +7712,7 @@ svgo@^1.0.0, svgo@^1.0.5: unquote "~1.1.1" util.promisify "~1.0.0" -symbol-observable@^1.1.0: +symbol-observable@^1.1.0, symbol-observable@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== From 61b4dc7dd388e79ad1e60f7c6f278e5c226eb0e4 Mon Sep 17 00:00:00 2001 From: alex-cannon Date: Thu, 21 Nov 2019 11:01:05 -0700 Subject: [PATCH 03/19] chore: updated index.jsx to index.tsx in index.html --- src/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.html b/src/index.html index 2bad8af..7c9b227 100644 --- a/src/index.html +++ b/src/index.html @@ -14,6 +14,6 @@
- + From 54bb23e3f54486305363dcdea522f177d2da6c62 Mon Sep 17 00:00:00 2001 From: alex-cannon Date: Thu, 21 Nov 2019 12:28:55 -0700 Subject: [PATCH 04/19] Milestone: Moving api logic to ~/core/store/services --- src/core/store/services/APIFetchReducer.ts | 25 ++++++++ src/core/store/services/actions.ts | 72 ++++++++++++++++++++++ src/core/{ => store}/store.ts | 10 +++ src/index.tsx | 4 +- 4 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 src/core/store/services/APIFetchReducer.ts create mode 100644 src/core/store/services/actions.ts rename src/core/{ => store}/store.ts (69%) diff --git a/src/core/store/services/APIFetchReducer.ts b/src/core/store/services/APIFetchReducer.ts new file mode 100644 index 0000000..53f9f63 --- /dev/null +++ b/src/core/store/services/APIFetchReducer.ts @@ -0,0 +1,25 @@ +// Reducer that handles state for the useAPI hook +interface iAction { + type: string; + payload: any; + errorMessage: string; +} + +interface iState { + loading: boolean; + errorMessage: string; + data: any; +} + +const APIFetchReducer = (state: iState, action: iAction) => { + switch (action.type) { + case "SUCCESS": + return { ...state, loading: false, data: action.payload }; + case "FAILURE": + return { ...state, loading: false, errorMessage: action.errorMessage }; + default: + throw new Error(); + } +}; + +export default APIFetchReducer; diff --git a/src/core/store/services/actions.ts b/src/core/store/services/actions.ts new file mode 100644 index 0000000..2598528 --- /dev/null +++ b/src/core/store/services/actions.ts @@ -0,0 +1,72 @@ +import axios from "axios"; +import { getSheetData } from "./utils"; +import { stringify } from "qs"; + +const SHEET_ID = + process.env.SHEET_ID || "1ZPRRR8T51Tk-Co8h_GBh3G_7P2F7ZrYxPQDSYycpCUg"; +const API_KEY = process.env.GOOGLE_API_KEY; + +const DEFAULT_ERROR_MESSAGE = "Something went wrong!"; + +// Create our API client and inject the API key into every request +const client = axios.create({ + baseURL: `https://sheets.googleapis.com/v4/spreadsheets/${SHEET_ID}/`, + params: { key: API_KEY }, +}); + +// Utility for fetching metadata about the sheets in the spreadsheet +const getSheetTitles = async () => { + // sheetMetadata will be a list of sheets: { "sheets": [ { "properties": { "title": "Index" } }, ... ] } + const sheetMetadata = await client.get("", { + params: { + fields: "sheets.properties.title", + }, + }); + return sheetMetadata.data.sheets + .map(sheet => sheet.properties.title) + .filter(title => title !== "Index"); +}; + +// Utility for fetching a single sheet from a spreadsheet by its title +const getSheetByTitle = async title => + await client.get("values:batchGet", { + params: { + majorDimension: "ROWS", + ranges: title, + }, + }); +/** + * Handlers for the various types of data we want from the Sheets API + * They should return parsed sheet data, rather than the raw response + * from the API. + */ +export default { + // Returns *all* services as a single array—for use in search + getAllServices: async () => { + const types = await getSheetTitles(); + const allServicesRes = await client.get("values:batchGet", { + params: { + majorDimension: "ROWS", + ranges: types, + }, + paramsSerializer: params => { + return stringify(params, { indices: false }); + }, + }); + const allServices = allServicesRes.data.valueRanges.reduce((list, type) => { + return [...list, ...type.values]; + }, []); + return allServices; + }, + // Returns the spreadsheet's index sheet + getIndex: async () => { + const res = await getSheetByTitle("Index"); + return getSheetData(res.data); + }, + // Returns a list of services for a given type + getServicesByType: async type => { + const res = await getSheetByTitle(type); + return getSheetData(res.data); + }, + DEFAULT_ERROR_MESSAGE, +}; diff --git a/src/core/store.ts b/src/core/store/store.ts similarity index 69% rename from src/core/store.ts rename to src/core/store/store.ts index de127c2..514aa8b 100644 --- a/src/core/store.ts +++ b/src/core/store/store.ts @@ -11,3 +11,13 @@ const defaultReducer = (state, action) => { export const configureStore = initialState => { return createStore(defaultReducer, initialState, applyMiddleware(thunk)); }; + +export const initializeStore = () => { + return configureStore({ + services: { + loading: false, + errorMessage: null, + data: null, + }, + }); +}; diff --git a/src/index.tsx b/src/index.tsx index 8c141ff..b155582 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -4,7 +4,7 @@ import { BrowserRouter, Redirect, Route, Switch } from "react-router-dom"; import { Provider } from "react-redux"; import styled, { createGlobalStyle, ThemeProvider } from "styled-components"; import { Normalize } from "styled-normalize"; -import { configureStore } from "~/core/store"; +import { initializeStore } from "~core/store/store"; import Nav from "~/components/nav"; import theme from "~/core/theme"; import Categories from "~/pages/categories"; @@ -26,7 +26,7 @@ const PageContainer = styled.div({ }); // Initialize Redux store -const store = configureStore({}); +const store = initializeStore(); const App = () => ( From 8b06a93494f15c70a3e3215d988d64edad8c03d2 Mon Sep 17 00:00:00 2001 From: alex-cannon Date: Thu, 21 Nov 2019 12:30:04 -0700 Subject: [PATCH 05/19] chore: installed @types/node --- package.json | 1 + yarn.lock | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/package.json b/package.json index 7e66b0d..4dcf276 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ }, "dependencies": { "@types/jest": "^24.0.23", + "@types/node": "^12.12.11", "axios": "0.18.1", "husky": "^2.4.0", "lodash": "^4.17.13", diff --git a/yarn.lock b/yarn.lock index d53fbf9..2186396 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1360,6 +1360,11 @@ dependencies: jest-diff "^24.3.0" +"@types/node@^12.12.11": + version "12.12.11" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.11.tgz#bec2961975888d964196bf0016a2f984d793d3ce" + integrity sha512-O+x6uIpa6oMNTkPuHDa9MhMMehlxLAd5QcOvKRjAFsBVpeFWTOPnXbDvILvFgFFZfQ1xh1EZi1FbXxUix+zpsQ== + "@types/normalize-package-data@^2.4.0": version "2.4.0" resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e" From 7a98ff63d07d2b4f3aab2362fddc851d9080b4cb Mon Sep 17 00:00:00 2001 From: alex-cannon Date: Thu, 21 Nov 2019 14:59:57 -0700 Subject: [PATCH 06/19] milestone: more work done on refactoring api.js --- src/core/store/services/APIFetchReducer.ts | 25 --------------- src/core/store/services/actions.ts | 37 ++++++++++++++++++---- src/core/store/services/reducers.ts | 37 ++++++++++++++++++++++ src/core/store/store.ts | 14 ++++---- 4 files changed, 74 insertions(+), 39 deletions(-) delete mode 100644 src/core/store/services/APIFetchReducer.ts create mode 100644 src/core/store/services/reducers.ts diff --git a/src/core/store/services/APIFetchReducer.ts b/src/core/store/services/APIFetchReducer.ts deleted file mode 100644 index 53f9f63..0000000 --- a/src/core/store/services/APIFetchReducer.ts +++ /dev/null @@ -1,25 +0,0 @@ -// Reducer that handles state for the useAPI hook -interface iAction { - type: string; - payload: any; - errorMessage: string; -} - -interface iState { - loading: boolean; - errorMessage: string; - data: any; -} - -const APIFetchReducer = (state: iState, action: iAction) => { - switch (action.type) { - case "SUCCESS": - return { ...state, loading: false, data: action.payload }; - case "FAILURE": - return { ...state, loading: false, errorMessage: action.errorMessage }; - default: - throw new Error(); - } -}; - -export default APIFetchReducer; diff --git a/src/core/store/services/actions.ts b/src/core/store/services/actions.ts index 2598528..02f3412 100644 --- a/src/core/store/services/actions.ts +++ b/src/core/store/services/actions.ts @@ -1,5 +1,5 @@ import axios from "axios"; -import { getSheetData } from "./utils"; +import { getSheetData } from "~/core/utils"; import { stringify } from "qs"; const SHEET_ID = @@ -35,14 +35,39 @@ const getSheetByTitle = async title => ranges: title, }, }); + /** - * Handlers for the various types of data we want from the Sheets API - * They should return parsed sheet data, rather than the raw response - * from the API. + * Actions relating to fetching the google spreadsheet. */ +export const getServicesSuccess = (payload: any) => ({ + type: "GET_SERVICES_SUCCESS", + payload, + errorMessage: null, +}); + +export const getServicesError = (errorMessage: string) => ({ + type: "GET_SERVICES_ERROR", + payload: null, + errorMessage, +}); + +export const getServicesLoading = () => ({ + type: "GET_SERVICES_LOADING", + payload: null, + errorMessage: null, +}); + export default { + /** + * Handlers for the various types of data we want from the Sheets API + * They should return parsed sheet data, rather than the raw response + * from the API. + */ // Returns *all* services as a single array—for use in search - getAllServices: async () => { + getAllServices: () => async ({ services }, dispatch) => { + // Check if we have the google sheet + if (services) return; + const types = await getSheetTitles(); const allServicesRes = await client.get("values:batchGet", { params: { @@ -56,7 +81,7 @@ export default { const allServices = allServicesRes.data.valueRanges.reduce((list, type) => { return [...list, ...type.values]; }, []); - return allServices; + dispatch(getServicesSuccess(allServices)); }, // Returns the spreadsheet's index sheet getIndex: async () => { diff --git a/src/core/store/services/reducers.ts b/src/core/store/services/reducers.ts new file mode 100644 index 0000000..1a5a61e --- /dev/null +++ b/src/core/store/services/reducers.ts @@ -0,0 +1,37 @@ +// Reducer that handles state for the useAPI hook +interface iAction { + type: string; + payload: any; + errorMessage: string; +} + +interface iState { + loading: boolean; + errorMessage: string; + data: any; +} + +const getServicesReducer = (state: iState, action: iAction) => { + switch (action.type) { + case "GET_SERVICES_LOADING": + return { ...state, loading: false, data: null, errorMessage: null }; + case "GET_SERVICES_ERROR": + return { + ...state, + loading: false, + data: null, + errorMessage: action.errorMessage, + }; + case "GET_SERVICES_SUCCESS": + return { + ...state, + loading: false, + data: action.payload, + errorMessage: null, + }; + default: + throw new Error(); + } +}; + +export default getServicesReducer; diff --git a/src/core/store/store.ts b/src/core/store/store.ts index 514aa8b..f72f02e 100644 --- a/src/core/store/store.ts +++ b/src/core/store/store.ts @@ -1,15 +1,13 @@ -import { createStore, applyMiddleware } from "redux"; +import { createStore, combineReducers, applyMiddleware } from "redux"; import thunk from "redux-thunk"; +import services from "~/core/store/services/reducers"; -// Temporary Reducer. Remove this. -const defaultReducer = (state, action) => { - let newAction = action; - Object.keys(newAction); - return state; -}; +const reducers = combineReducers({ + services, +}); export const configureStore = initialState => { - return createStore(defaultReducer, initialState, applyMiddleware(thunk)); + return createStore(reducers, initialState, applyMiddleware(thunk)); }; export const initializeStore = () => { From 7344f023ff0a303f56c0281439db148f9c7446c1 Mon Sep 17 00:00:00 2001 From: alex-cannon Date: Thu, 21 Nov 2019 16:10:45 -0700 Subject: [PATCH 07/19] milestone: have broken categories page WIP --- src/core/interfaces/asyncAction.ts | 11 ++++++++ src/core/store/services/actions.ts | 38 +++++++++++++++++--------- src/core/store/services/reducers.ts | 21 +++++--------- src/core/store/services/useServices.ts | 22 +++++++++++++++ src/core/store/store.ts | 9 ++---- src/pages/categories/index.jsx | 8 ++++-- src/pages/search/index.jsx | 6 ++-- 7 files changed, 75 insertions(+), 40 deletions(-) create mode 100644 src/core/interfaces/asyncAction.ts create mode 100644 src/core/store/services/useServices.ts diff --git a/src/core/interfaces/asyncAction.ts b/src/core/interfaces/asyncAction.ts new file mode 100644 index 0000000..176204d --- /dev/null +++ b/src/core/interfaces/asyncAction.ts @@ -0,0 +1,11 @@ +export interface asyncAction { + type: string; + payload: any; + errorMessage: string; +} + +export interface asyncState { + loading: boolean; + errorMessage: string; + data: any; +} diff --git a/src/core/store/services/actions.ts b/src/core/store/services/actions.ts index 02f3412..b791c0d 100644 --- a/src/core/store/services/actions.ts +++ b/src/core/store/services/actions.ts @@ -57,17 +57,22 @@ export const getServicesLoading = () => ({ errorMessage: null, }); -export default { - /** - * Handlers for the various types of data we want from the Sheets API - * They should return parsed sheet data, rather than the raw response - * from the API. - */ - // Returns *all* services as a single array—for use in search - getAllServices: () => async ({ services }, dispatch) => { - // Check if we have the google sheet - if (services) return; +/** + * Handlers for the various types of data we want from the Sheets API + * They should return parsed sheet data, rather than the raw response + * from the API. + */ +// Fetches all services and updates global state. "redux-thunk" action. +export const getAllServices = () => async (dispatch, getState) => { + // Check if we have the google sheet + const services = getState().services; + if (services.data) + return dispatch(getServicesError("Error: all services already loaded.")); + let allServices = []; + + try { + dispatch(getServicesLoading()); const types = await getSheetTitles(); const allServicesRes = await client.get("values:batchGet", { params: { @@ -78,11 +83,18 @@ export default { return stringify(params, { indices: false }); }, }); - const allServices = allServicesRes.data.valueRanges.reduce((list, type) => { + allServices = allServicesRes.data.valueRanges.reduce((list, type) => { return [...list, ...type.values]; }, []); - dispatch(getServicesSuccess(allServices)); - }, + } catch (e) { + // Dispatch a 'failure' action if the request failed + return dispatch(getServicesError(DEFAULT_ERROR_MESSAGE)); + } + + dispatch(getServicesSuccess(allServices)); +}; + +export default { // Returns the spreadsheet's index sheet getIndex: async () => { const res = await getSheetByTitle("Index"); diff --git a/src/core/store/services/reducers.ts b/src/core/store/services/reducers.ts index 1a5a61e..a560966 100644 --- a/src/core/store/services/reducers.ts +++ b/src/core/store/services/reducers.ts @@ -1,17 +1,10 @@ -// Reducer that handles state for the useAPI hook -interface iAction { - type: string; - payload: any; - errorMessage: string; -} - -interface iState { - loading: boolean; - errorMessage: string; - data: any; -} +import { asyncState, asyncAction } from "~/core/interfaces/asyncAction"; -const getServicesReducer = (state: iState, action: iAction) => { +// Reducer that handles state for the useAPI hook +const getServicesReducer = ( + state: asyncState = { loading: false, errorMessage: null, data: null }, + action: asyncAction +) => { switch (action.type) { case "GET_SERVICES_LOADING": return { ...state, loading: false, data: null, errorMessage: null }; @@ -30,7 +23,7 @@ const getServicesReducer = (state: iState, action: iAction) => { errorMessage: null, }; default: - throw new Error(); + return state; } }; diff --git a/src/core/store/services/useServices.ts b/src/core/store/services/useServices.ts new file mode 100644 index 0000000..0a32f45 --- /dev/null +++ b/src/core/store/services/useServices.ts @@ -0,0 +1,22 @@ +import { useEffect } from "react"; +import { useSelector, useDispatch } from "react-redux"; +import { getAllServices } from "./actions"; + +/** + * Hook to use all services data. Service data is parsed into an object. + * Services object: { loading, errorMessage, data } + */ +const useServices = () => { + const services = useSelector(state => state.services); + const dispatch = useDispatch(); + + // Equivilent to componentDidMount + useEffect(() => { + if (services.data) return; + dispatch(getAllServices()); + }, []); + + return services; +}; + +export default useServices; diff --git a/src/core/store/store.ts b/src/core/store/store.ts index f72f02e..ec79bfa 100644 --- a/src/core/store/store.ts +++ b/src/core/store/store.ts @@ -11,11 +11,6 @@ export const configureStore = initialState => { }; export const initializeStore = () => { - return configureStore({ - services: { - loading: false, - errorMessage: null, - data: null, - }, - }); + // State is intialized per reducer + return configureStore({}); }; diff --git a/src/pages/categories/index.jsx b/src/pages/categories/index.jsx index 7f954a0..75e7e64 100644 --- a/src/pages/categories/index.jsx +++ b/src/pages/categories/index.jsx @@ -1,10 +1,10 @@ import React, { Fragment } from "react"; +import useServices from "~/core/store/services/useServices"; import styled from "styled-components"; import Logo from "~/components/logo"; import Loader from "~/components/loader"; import Error from "~/components/error"; import { H1 } from "~/components/typography"; -import api, { useAPI } from "~/core/api"; import CategoryCard from "./category-card"; const StyledLogo = styled(Logo)({ @@ -39,7 +39,7 @@ const getCategories = data => ); const Categories = () => { - const { loading, errorMessage, data } = useAPI(api.getIndex); + const { loading, errorMessage, data } = useServices(); if (errorMessage) { return ; @@ -51,12 +51,14 @@ const Categories = () => { Pick a category to see services in your area. {loading ? ( - ) : ( + ) : data ? ( {getCategories(data).map(c => ( ))} + ) : ( + "Oops, something went wrong!" )} ); diff --git a/src/pages/search/index.jsx b/src/pages/search/index.jsx index 5c5fb5c..33672b9 100644 --- a/src/pages/search/index.jsx +++ b/src/pages/search/index.jsx @@ -4,7 +4,7 @@ import Logo from "~/components/logo"; import Loader from "~/components/loader"; import InputAndSubmit from "~/components/inputAndSubmit"; import { H1 } from "~/components/typography"; -import api, { useAPI } from "~/core/api"; +import { useSelector } from "react-redux"; import ServiceCard from "~/pages/services/service-card"; import { formatService } from "~/core/utils"; @@ -58,8 +58,8 @@ const queryServices = (data, query) => { }; const Search = () => { - const { loading, error, data } = useAPI(api.getAllServices); - const index = useAPI(api.getIndex); + const { loading, error, data } = useSelector(state => state.services); + const index = { error: null }; const urlQuery = new URLSearchParams(location.search); const [searchValue, setSearchValue] = useState(urlQuery.get("s") || ""); From 82db9ea574d433299de679f70629e2dfa99d312a Mon Sep 17 00:00:00 2001 From: alex-cannon Date: Sun, 24 Nov 2019 13:07:43 -0700 Subject: [PATCH 08/19] get categories works --- src/core/store/services/actions.ts | 104 +++++++++++++++---------- src/core/store/services/reducers.ts | 28 ++++++- src/core/store/services/useServices.ts | 26 ++++++- src/core/store/store.ts | 6 +- src/pages/categories/index.jsx | 10 +-- 5 files changed, 122 insertions(+), 52 deletions(-) diff --git a/src/core/store/services/actions.ts b/src/core/store/services/actions.ts index b791c0d..e6529fa 100644 --- a/src/core/store/services/actions.ts +++ b/src/core/store/services/actions.ts @@ -36,43 +36,19 @@ const getSheetByTitle = async title => }, }); -/** - * Actions relating to fetching the google spreadsheet. - */ -export const getServicesSuccess = (payload: any) => ({ - type: "GET_SERVICES_SUCCESS", - payload, - errorMessage: null, -}); - -export const getServicesError = (errorMessage: string) => ({ - type: "GET_SERVICES_ERROR", - payload: null, - errorMessage, -}); - -export const getServicesLoading = () => ({ - type: "GET_SERVICES_LOADING", - payload: null, - errorMessage: null, -}); - /** * Handlers for the various types of data we want from the Sheets API * They should return parsed sheet data, rather than the raw response * from the API. */ // Fetches all services and updates global state. "redux-thunk" action. -export const getAllServices = () => async (dispatch, getState) => { - // Check if we have the google sheet - const services = getState().services; - if (services.data) - return dispatch(getServicesError("Error: all services already loaded.")); - +export const getAllServices = () => async (dispatch: Function) => { let allServices = []; + // Dispatch Loading action + dispatch(getServicesLoading()); + try { - dispatch(getServicesLoading()); const types = await getSheetTitles(); const allServicesRes = await client.get("values:batchGet", { params: { @@ -83,27 +59,73 @@ export const getAllServices = () => async (dispatch, getState) => { return stringify(params, { indices: false }); }, }); - allServices = allServicesRes.data.valueRanges.reduce((list, type) => { + /*allServices = allServicesRes.data.valueRanges.reduce((list, type) => { return [...list, ...type.values]; - }, []); + }, []);*/ + allServices = getSheetData(allServicesRes.data); } catch (e) { // Dispatch a 'failure' action if the request failed return dispatch(getServicesError(DEFAULT_ERROR_MESSAGE)); } + // Dispatch services data dispatch(getServicesSuccess(allServices)); }; -export default { - // Returns the spreadsheet's index sheet - getIndex: async () => { +// Returns the spreadsheet's index sheet +export const getServicesIndex = () => async (dispatch: Function) => { + dispatch(getServicesIndexLoading()); + try { const res = await getSheetByTitle("Index"); - return getSheetData(res.data); - }, - // Returns a list of services for a given type - getServicesByType: async type => { - const res = await getSheetByTitle(type); - return getSheetData(res.data); - }, - DEFAULT_ERROR_MESSAGE, + return dispatch(getServicesIndexSuccess(getSheetData(res.data))); + } catch (e) { + return dispatch(getServicesIndexError(e)); + } }; + +// Returns the spreadsheet's services by type +export const getServicesByType = () => async type => { + const res = await getSheetByTitle(type); + return getSheetData(res.data); +}; + +// Default error message +export { DEFAULT_ERROR_MESSAGE }; + +// getAllServices actions +export const getServicesSuccess = (payload: any) => ({ + type: "GET_SERVICES_SUCCESS", + payload, + errorMessage: null, +}); + +export const getServicesError = (errorMessage: string) => ({ + type: "GET_SERVICES_ERROR", + payload: null, + errorMessage, +}); + +export const getServicesLoading = () => ({ + type: "GET_SERVICES_LOADING", + payload: null, + errorMessage: null, +}); + +// getServicesIndex actions +export const getServicesIndexSuccess = (payload: any) => ({ + type: "GET_SERVICES_INDEX_SUCCESS", + payload, + errorMessage: null, +}); + +export const getServicesIndexError = (errorMessage: string) => ({ + type: "GET_SERVICES_INDEX_ERROR", + payload: null, + errorMessage, +}); + +export const getServicesIndexLoading = () => ({ + type: "GET_SERVICES_INDEX_LOADING", + payload: null, + errorMessage: null, +}); diff --git a/src/core/store/services/reducers.ts b/src/core/store/services/reducers.ts index a560966..a5d686f 100644 --- a/src/core/store/services/reducers.ts +++ b/src/core/store/services/reducers.ts @@ -1,7 +1,7 @@ import { asyncState, asyncAction } from "~/core/interfaces/asyncAction"; // Reducer that handles state for the useAPI hook -const getServicesReducer = ( +export const getServicesReducer = ( state: asyncState = { loading: false, errorMessage: null, data: null }, action: asyncAction ) => { @@ -27,4 +27,30 @@ const getServicesReducer = ( } }; +export const getServicesIndex = ( + state: asyncState = { loading: false, errorMessage: null, data: null }, + action: asyncAction +) => { + switch (action.type) { + case "GET_SERVICES_INDEX_LOADING": + return { ...state, loading: false, data: null, errorMessage: null }; + case "GET_SERVICES_INDEX_ERROR": + return { + ...state, + loading: false, + data: null, + errorMessage: action.errorMessage, + }; + case "GET_SERVICES_INDEX_SUCCESS": + return { + ...state, + loading: false, + data: action.payload, + errorMessage: null, + }; + default: + return state; + } +}; + export default getServicesReducer; diff --git a/src/core/store/services/useServices.ts b/src/core/store/services/useServices.ts index 0a32f45..311cb60 100644 --- a/src/core/store/services/useServices.ts +++ b/src/core/store/services/useServices.ts @@ -1,17 +1,19 @@ import { useEffect } from "react"; import { useSelector, useDispatch } from "react-redux"; -import { getAllServices } from "./actions"; +import { getAllServices, getServicesIndex } from "./actions"; /** * Hook to use all services data. Service data is parsed into an object. - * Services object: { loading, errorMessage, data } + * For example, { loading, errorMessage, data: {...} } + * See ~/core/interfaces/formattedService.ts for more info. */ -const useServices = () => { +export const useServices = () => { const services = useSelector(state => state.services); const dispatch = useDispatch(); // Equivilent to componentDidMount useEffect(() => { + // Do not make additional requests for services. if (services.data) return; dispatch(getAllServices()); }, []); @@ -19,4 +21,22 @@ const useServices = () => { return services; }; +/** + * Hook to fetch index sheet data. Used to define categories. + */ +export const useServicesIndex = () => { + const { loading = false, errorMessage = null, data = null } = useSelector( + state => state.servicesIndex + ); + const dispatch = useDispatch(); + + useEffect(() => { + // Don't make additional requests. + if (data) return; + dispatch(getServicesIndex()); + }, []); + + return { loading, errorMessage, data }; +}; + export default useServices; diff --git a/src/core/store/store.ts b/src/core/store/store.ts index ec79bfa..4dc74d5 100644 --- a/src/core/store/store.ts +++ b/src/core/store/store.ts @@ -1,9 +1,13 @@ import { createStore, combineReducers, applyMiddleware } from "redux"; import thunk from "redux-thunk"; -import services from "~/core/store/services/reducers"; +import { + getServicesReducer as services, + getServicesIndex as servicesIndex, +} from "~/core/store/services/reducers"; const reducers = combineReducers({ services, + servicesIndex, }); export const configureStore = initialState => { diff --git a/src/pages/categories/index.jsx b/src/pages/categories/index.jsx index 75e7e64..d8f4237 100644 --- a/src/pages/categories/index.jsx +++ b/src/pages/categories/index.jsx @@ -1,5 +1,5 @@ import React, { Fragment } from "react"; -import useServices from "~/core/store/services/useServices"; +import { useServicesIndex } from "~/core/store/services/useServices"; import styled from "styled-components"; import Logo from "~/components/logo"; import Loader from "~/components/loader"; @@ -39,7 +39,7 @@ const getCategories = data => ); const Categories = () => { - const { loading, errorMessage, data } = useServices(); + const { loading, errorMessage, data } = useServicesIndex(); if (errorMessage) { return ; @@ -49,16 +49,14 @@ const Categories = () => { Pick a category to see services in your area. - {loading ? ( + {loading || !data ? ( - ) : data ? ( + ) : ( {getCategories(data).map(c => ( ))} - ) : ( - "Oops, something went wrong!" )} ); From 2e608ed0fa7769d84ab6949cc2cfc5cdf9cc38af Mon Sep 17 00:00:00 2001 From: alex-cannon Date: Sun, 1 Dec 2019 16:34:16 -0700 Subject: [PATCH 09/19] minor changes --- src/core/store/services/useServices.ts | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/core/store/services/useServices.ts b/src/core/store/services/useServices.ts index 311cb60..4a2fbf9 100644 --- a/src/core/store/services/useServices.ts +++ b/src/core/store/services/useServices.ts @@ -11,10 +11,10 @@ export const useServices = () => { const services = useSelector(state => state.services); const dispatch = useDispatch(); - // Equivilent to componentDidMount + // FETCH DATA 3 ATTEMPTS WHENEVER data = null && loading = false useEffect(() => { // Do not make additional requests for services. - if (services.data) return; + if (services && services.data) return; dispatch(getAllServices()); }, []); @@ -39,4 +39,21 @@ export const useServicesIndex = () => { return { loading, errorMessage, data }; }; +/** + * Hooks for Categories, Types, and Services pages. + * Lifecycle: + * 1. GET all google sheets + * 2. Store globally in array + * 3. Hooks pull their data from the array + */ + +// Returns a list of categories +export const useCategories = () => {}; + +// Returns a list of subtypes for a category +//export const useSubTypes = category => {}; + +// Returns a list of services for a subtype +//export const useSubTypeServices = subtype => {}; + export default useServices; From a93428dd7f8eaa1a32980b3d96972542871299b9 Mon Sep 17 00:00:00 2001 From: alex-cannon Date: Sun, 22 Dec 2019 15:12:36 -0700 Subject: [PATCH 10/19] feat: categories and categories -> types use redux --- package.json | 4 +- src/core/store/services/actions.ts | 3 -- src/core/store/services/reducers.ts | 6 +-- src/core/store/services/useServices.ts | 39 ++++--------------- src/core/utils.ts | 6 +-- src/pages/categories/index.jsx | 4 +- src/pages/types/index.jsx | 4 +- src/{core/interfaces => types}/asyncAction.ts | 0 .../interfaces => types}/formattedService.ts | 0 yarn.lock | 11 +++--- 10 files changed, 24 insertions(+), 53 deletions(-) rename src/{core/interfaces => types}/asyncAction.ts (100%) rename src/{core/interfaces => types}/formattedService.ts (100%) diff --git a/package.json b/package.json index 4dcf276..0bb191a 100644 --- a/package.json +++ b/package.json @@ -55,11 +55,11 @@ "lodash": "^4.17.13", "polished": "^3.3.0", "qs": "^6.7.0", - "react": "^16.8.6", + "react": "^16.8.3", "react-dom": "^16.8.6", "react-redux": "^7.1.3", "react-router-dom": "^5.0.0", - "redux": "^4.0.4", + "redux": "^2.0.0 || ^3.0.0 || ^4.0.0-0", "redux-thunk": "^2.3.0", "styled-components": "^4.2.0", "styled-normalize": "^8.0.6", diff --git a/src/core/store/services/actions.ts b/src/core/store/services/actions.ts index e6529fa..3098bae 100644 --- a/src/core/store/services/actions.ts +++ b/src/core/store/services/actions.ts @@ -59,9 +59,6 @@ export const getAllServices = () => async (dispatch: Function) => { return stringify(params, { indices: false }); }, }); - /*allServices = allServicesRes.data.valueRanges.reduce((list, type) => { - return [...list, ...type.values]; - }, []);*/ allServices = getSheetData(allServicesRes.data); } catch (e) { // Dispatch a 'failure' action if the request failed diff --git a/src/core/store/services/reducers.ts b/src/core/store/services/reducers.ts index a5d686f..57331e4 100644 --- a/src/core/store/services/reducers.ts +++ b/src/core/store/services/reducers.ts @@ -1,4 +1,4 @@ -import { asyncState, asyncAction } from "~/core/interfaces/asyncAction"; +import { asyncState, asyncAction } from "~types/asyncAction"; // Reducer that handles state for the useAPI hook export const getServicesReducer = ( @@ -28,12 +28,12 @@ export const getServicesReducer = ( }; export const getServicesIndex = ( - state: asyncState = { loading: false, errorMessage: null, data: null }, + state: asyncState = { loading: true, errorMessage: null, data: null }, action: asyncAction ) => { switch (action.type) { case "GET_SERVICES_INDEX_LOADING": - return { ...state, loading: false, data: null, errorMessage: null }; + return { ...state, loading: true, data: null, errorMessage: null }; case "GET_SERVICES_INDEX_ERROR": return { ...state, diff --git a/src/core/store/services/useServices.ts b/src/core/store/services/useServices.ts index 4a2fbf9..227a520 100644 --- a/src/core/store/services/useServices.ts +++ b/src/core/store/services/useServices.ts @@ -2,18 +2,14 @@ import { useEffect } from "react"; import { useSelector, useDispatch } from "react-redux"; import { getAllServices, getServicesIndex } from "./actions"; -/** - * Hook to use all services data. Service data is parsed into an object. - * For example, { loading, errorMessage, data: {...} } - * See ~/core/interfaces/formattedService.ts for more info. - */ -export const useServices = () => { +import { asyncState } from "~/types/asyncAction"; +import { formattedService } from "~/types/formattedService"; + +export const useServices = (): formattedService[] => { const services = useSelector(state => state.services); const dispatch = useDispatch(); - // FETCH DATA 3 ATTEMPTS WHENEVER data = null && loading = false useEffect(() => { - // Do not make additional requests for services. if (services && services.data) return; dispatch(getAllServices()); }, []); @@ -21,17 +17,13 @@ export const useServices = () => { return services; }; -/** - * Hook to fetch index sheet data. Used to define categories. - */ -export const useServicesIndex = () => { - const { loading = false, errorMessage = null, data = null } = useSelector( +export const useServicesIndex = (): asyncState => { + const dispatch = useDispatch(); + const { loading, errorMessage, data } = useSelector( state => state.servicesIndex ); - const dispatch = useDispatch(); useEffect(() => { - // Don't make additional requests. if (data) return; dispatch(getServicesIndex()); }, []); @@ -39,21 +31,4 @@ export const useServicesIndex = () => { return { loading, errorMessage, data }; }; -/** - * Hooks for Categories, Types, and Services pages. - * Lifecycle: - * 1. GET all google sheets - * 2. Store globally in array - * 3. Hooks pull their data from the array - */ - -// Returns a list of categories -export const useCategories = () => {}; - -// Returns a list of subtypes for a category -//export const useSubTypes = category => {}; - -// Returns a list of services for a subtype -//export const useSubTypeServices = subtype => {}; - export default useServices; diff --git a/src/core/utils.ts b/src/core/utils.ts index f41f705..5f73bfe 100644 --- a/src/core/utils.ts +++ b/src/core/utils.ts @@ -1,11 +1,11 @@ -import formattedService from "./interfaces/formattedService"; +import formattedService from "../types/formattedService"; interface iSheet { - valueRanges: Array; + valueRanges: any[]; } // Returns the rows of data from a sheet (excluding the header row) -export const getSheetData = (sheet: iSheet): Array => { +export const getSheetData = (sheet: iSheet): formattedService[] => { const items = sheet.valueRanges[0].values; // Remove the header row items.shift(); diff --git a/src/pages/categories/index.jsx b/src/pages/categories/index.jsx index d8f4237..b0df9e7 100644 --- a/src/pages/categories/index.jsx +++ b/src/pages/categories/index.jsx @@ -53,8 +53,8 @@ const Categories = () => { ) : ( - {getCategories(data).map(c => ( - + {getCategories(data).map(category => ( + ))} )} diff --git a/src/pages/types/index.jsx b/src/pages/types/index.jsx index 22e3149..450b021 100644 --- a/src/pages/types/index.jsx +++ b/src/pages/types/index.jsx @@ -3,8 +3,8 @@ import styled from "styled-components"; import Loader from "~/components/loader"; import Error from "~/components/error"; import TitleBar from "~/components/title-bar"; -import api, { useAPI } from "~/core/api"; import TypeCard from "./type-card"; +import { useServicesIndex } from "~/core/store/services/useServices"; const TypesList = styled.ul({ listStyle: "none", @@ -13,8 +13,8 @@ const TypesList = styled.ul({ }); const Types = ({ match }) => { + const { loading, errorMessage, data } = useServicesIndex(); const { categoryId } = match.params; - const { loading, errorMessage, data } = useAPI(api.getIndex); if (loading) { return ; diff --git a/src/core/interfaces/asyncAction.ts b/src/types/asyncAction.ts similarity index 100% rename from src/core/interfaces/asyncAction.ts rename to src/types/asyncAction.ts diff --git a/src/core/interfaces/formattedService.ts b/src/types/formattedService.ts similarity index 100% rename from src/core/interfaces/formattedService.ts rename to src/types/formattedService.ts diff --git a/yarn.lock b/yarn.lock index 2186396..4e4f5b3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6725,15 +6725,14 @@ react-router@5.0.0: tiny-invariant "^1.0.2" tiny-warning "^1.0.0" -react@^16.8.6: - version "16.8.6" - resolved "https://registry.yarnpkg.com/react/-/react-16.8.6.tgz#ad6c3a9614fd3a4e9ef51117f54d888da01f2bbe" - integrity sha512-pC0uMkhLaHm11ZSJULfOBqV4tIZkx87ZLvbbQYunNixAAvjnC+snJCg0XQXn9VIsttVsbZP/H/ewzgsd5fxKXw== +react@^16.8.3: + version "16.12.0" + resolved "https://registry.yarnpkg.com/react/-/react-16.12.0.tgz#0c0a9c6a142429e3614834d5a778e18aa78a0b83" + integrity sha512-fglqy3k5E+81pA8s+7K0/T3DBCF0ZDOher1elBFzF7O6arXJgzyu/FW+COxFvAWXJoJN9KIZbT2LXlukwphYTA== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" prop-types "^15.6.2" - scheduler "^0.13.6" read-pkg-up@^4.0.0: version "4.0.0" @@ -6805,7 +6804,7 @@ redux-thunk@^2.3.0: resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622" integrity sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw== -redux@^4.0.4: +"redux@^2.0.0 || ^3.0.0 || ^4.0.0-0": version "4.0.4" resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.4.tgz#4ee1aeb164b63d6a1bcc57ae4aa0b6e6fa7a3796" integrity sha512-vKv4WdiJxOWKxK0yRoaK3Y4pxxB0ilzVx6dszU2W8wLxlb2yikRph4iV/ymtdJ6ZxpBLFbyrxklnT5yBbQSl3Q== From d53ed35eae2441bd4bdb5e8515b17e733f33e4d8 Mon Sep 17 00:00:00 2001 From: alex-cannon Date: Mon, 23 Dec 2019 07:56:44 -0700 Subject: [PATCH 11/19] chore: re-organized some api/redux code --- src/core/{store => api}/services/actions.ts | 0 src/core/{store => api}/services/reducers.ts | 0 src/core/{store => api}/services/useServices.ts | 1 + src/core/{store => }/store.ts | 2 +- src/index.tsx | 2 +- src/pages/categories/index.jsx | 2 +- src/pages/types/index.jsx | 2 +- 7 files changed, 5 insertions(+), 4 deletions(-) rename src/core/{store => api}/services/actions.ts (100%) rename src/core/{store => api}/services/reducers.ts (100%) rename src/core/{store => api}/services/useServices.ts (93%) rename src/core/{store => }/store.ts (92%) diff --git a/src/core/store/services/actions.ts b/src/core/api/services/actions.ts similarity index 100% rename from src/core/store/services/actions.ts rename to src/core/api/services/actions.ts diff --git a/src/core/store/services/reducers.ts b/src/core/api/services/reducers.ts similarity index 100% rename from src/core/store/services/reducers.ts rename to src/core/api/services/reducers.ts diff --git a/src/core/store/services/useServices.ts b/src/core/api/services/useServices.ts similarity index 93% rename from src/core/store/services/useServices.ts rename to src/core/api/services/useServices.ts index 227a520..7328e86 100644 --- a/src/core/store/services/useServices.ts +++ b/src/core/api/services/useServices.ts @@ -17,6 +17,7 @@ export const useServices = (): formattedService[] => { return services; }; +// This should pull the global google sheet and use that data export const useServicesIndex = (): asyncState => { const dispatch = useDispatch(); const { loading, errorMessage, data } = useSelector( diff --git a/src/core/store/store.ts b/src/core/store.ts similarity index 92% rename from src/core/store/store.ts rename to src/core/store.ts index 4dc74d5..de55740 100644 --- a/src/core/store/store.ts +++ b/src/core/store.ts @@ -3,7 +3,7 @@ import thunk from "redux-thunk"; import { getServicesReducer as services, getServicesIndex as servicesIndex, -} from "~/core/store/services/reducers"; +} from "~core/api/services/reducers"; const reducers = combineReducers({ services, diff --git a/src/index.tsx b/src/index.tsx index b155582..fb4dd30 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -4,7 +4,7 @@ import { BrowserRouter, Redirect, Route, Switch } from "react-router-dom"; import { Provider } from "react-redux"; import styled, { createGlobalStyle, ThemeProvider } from "styled-components"; import { Normalize } from "styled-normalize"; -import { initializeStore } from "~core/store/store"; +import { initializeStore } from "~core/store"; import Nav from "~/components/nav"; import theme from "~/core/theme"; import Categories from "~/pages/categories"; diff --git a/src/pages/categories/index.jsx b/src/pages/categories/index.jsx index b0df9e7..e1c4eb3 100644 --- a/src/pages/categories/index.jsx +++ b/src/pages/categories/index.jsx @@ -1,5 +1,5 @@ import React, { Fragment } from "react"; -import { useServicesIndex } from "~/core/store/services/useServices"; +import { useServicesIndex } from "~/core/api/services/useServices"; import styled from "styled-components"; import Logo from "~/components/logo"; import Loader from "~/components/loader"; diff --git a/src/pages/types/index.jsx b/src/pages/types/index.jsx index 450b021..1c012e2 100644 --- a/src/pages/types/index.jsx +++ b/src/pages/types/index.jsx @@ -4,7 +4,7 @@ import Loader from "~/components/loader"; import Error from "~/components/error"; import TitleBar from "~/components/title-bar"; import TypeCard from "./type-card"; -import { useServicesIndex } from "~/core/store/services/useServices"; +import { useServicesIndex } from "~/core/api/services/useServices"; const TypesList = styled.ul({ listStyle: "none", From fe4e505a2fb3b033b9f801f369b66b9da30abee0 Mon Sep 17 00:00:00 2001 From: alex-cannon Date: Sat, 28 Dec 2019 13:25:36 -0700 Subject: [PATCH 12/19] chore: fixed eslint errors --- src/core/api/services/actions.ts | 128 ------------------ src/core/api/services/reducers.ts | 56 -------- src/core/api/services/useServices.ts | 103 +++++++++++--- src/core/api/services/useServicesIndex.ts | 73 ++++++++++ src/core/constants.ts | 13 ++ src/core/store.ts | 6 +- src/pages/categories/index.jsx | 2 +- src/pages/service-detail/index.jsx | 15 +- src/pages/services/index.jsx | 34 +++-- src/pages/types/index.jsx | 2 +- src/types/asyncAction.ts | 11 -- .../index.ts} | 16 ++- 12 files changed, 216 insertions(+), 243 deletions(-) delete mode 100644 src/core/api/services/actions.ts delete mode 100644 src/core/api/services/reducers.ts create mode 100644 src/core/api/services/useServicesIndex.ts create mode 100644 src/core/constants.ts delete mode 100644 src/types/asyncAction.ts rename src/types/{formattedService.ts => services/index.ts} (60%) diff --git a/src/core/api/services/actions.ts b/src/core/api/services/actions.ts deleted file mode 100644 index 3098bae..0000000 --- a/src/core/api/services/actions.ts +++ /dev/null @@ -1,128 +0,0 @@ -import axios from "axios"; -import { getSheetData } from "~/core/utils"; -import { stringify } from "qs"; - -const SHEET_ID = - process.env.SHEET_ID || "1ZPRRR8T51Tk-Co8h_GBh3G_7P2F7ZrYxPQDSYycpCUg"; -const API_KEY = process.env.GOOGLE_API_KEY; - -const DEFAULT_ERROR_MESSAGE = "Something went wrong!"; - -// Create our API client and inject the API key into every request -const client = axios.create({ - baseURL: `https://sheets.googleapis.com/v4/spreadsheets/${SHEET_ID}/`, - params: { key: API_KEY }, -}); - -// Utility for fetching metadata about the sheets in the spreadsheet -const getSheetTitles = async () => { - // sheetMetadata will be a list of sheets: { "sheets": [ { "properties": { "title": "Index" } }, ... ] } - const sheetMetadata = await client.get("", { - params: { - fields: "sheets.properties.title", - }, - }); - return sheetMetadata.data.sheets - .map(sheet => sheet.properties.title) - .filter(title => title !== "Index"); -}; - -// Utility for fetching a single sheet from a spreadsheet by its title -const getSheetByTitle = async title => - await client.get("values:batchGet", { - params: { - majorDimension: "ROWS", - ranges: title, - }, - }); - -/** - * Handlers for the various types of data we want from the Sheets API - * They should return parsed sheet data, rather than the raw response - * from the API. - */ -// Fetches all services and updates global state. "redux-thunk" action. -export const getAllServices = () => async (dispatch: Function) => { - let allServices = []; - - // Dispatch Loading action - dispatch(getServicesLoading()); - - try { - const types = await getSheetTitles(); - const allServicesRes = await client.get("values:batchGet", { - params: { - majorDimension: "ROWS", - ranges: types, - }, - paramsSerializer: params => { - return stringify(params, { indices: false }); - }, - }); - allServices = getSheetData(allServicesRes.data); - } catch (e) { - // Dispatch a 'failure' action if the request failed - return dispatch(getServicesError(DEFAULT_ERROR_MESSAGE)); - } - - // Dispatch services data - dispatch(getServicesSuccess(allServices)); -}; - -// Returns the spreadsheet's index sheet -export const getServicesIndex = () => async (dispatch: Function) => { - dispatch(getServicesIndexLoading()); - try { - const res = await getSheetByTitle("Index"); - return dispatch(getServicesIndexSuccess(getSheetData(res.data))); - } catch (e) { - return dispatch(getServicesIndexError(e)); - } -}; - -// Returns the spreadsheet's services by type -export const getServicesByType = () => async type => { - const res = await getSheetByTitle(type); - return getSheetData(res.data); -}; - -// Default error message -export { DEFAULT_ERROR_MESSAGE }; - -// getAllServices actions -export const getServicesSuccess = (payload: any) => ({ - type: "GET_SERVICES_SUCCESS", - payload, - errorMessage: null, -}); - -export const getServicesError = (errorMessage: string) => ({ - type: "GET_SERVICES_ERROR", - payload: null, - errorMessage, -}); - -export const getServicesLoading = () => ({ - type: "GET_SERVICES_LOADING", - payload: null, - errorMessage: null, -}); - -// getServicesIndex actions -export const getServicesIndexSuccess = (payload: any) => ({ - type: "GET_SERVICES_INDEX_SUCCESS", - payload, - errorMessage: null, -}); - -export const getServicesIndexError = (errorMessage: string) => ({ - type: "GET_SERVICES_INDEX_ERROR", - payload: null, - errorMessage, -}); - -export const getServicesIndexLoading = () => ({ - type: "GET_SERVICES_INDEX_LOADING", - payload: null, - errorMessage: null, -}); diff --git a/src/core/api/services/reducers.ts b/src/core/api/services/reducers.ts deleted file mode 100644 index 57331e4..0000000 --- a/src/core/api/services/reducers.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { asyncState, asyncAction } from "~types/asyncAction"; - -// Reducer that handles state for the useAPI hook -export const getServicesReducer = ( - state: asyncState = { loading: false, errorMessage: null, data: null }, - action: asyncAction -) => { - switch (action.type) { - case "GET_SERVICES_LOADING": - return { ...state, loading: false, data: null, errorMessage: null }; - case "GET_SERVICES_ERROR": - return { - ...state, - loading: false, - data: null, - errorMessage: action.errorMessage, - }; - case "GET_SERVICES_SUCCESS": - return { - ...state, - loading: false, - data: action.payload, - errorMessage: null, - }; - default: - return state; - } -}; - -export const getServicesIndex = ( - state: asyncState = { loading: true, errorMessage: null, data: null }, - action: asyncAction -) => { - switch (action.type) { - case "GET_SERVICES_INDEX_LOADING": - return { ...state, loading: true, data: null, errorMessage: null }; - case "GET_SERVICES_INDEX_ERROR": - return { - ...state, - loading: false, - data: null, - errorMessage: action.errorMessage, - }; - case "GET_SERVICES_INDEX_SUCCESS": - return { - ...state, - loading: false, - data: action.payload, - errorMessage: null, - }; - default: - return state; - } -}; - -export default getServicesReducer; diff --git a/src/core/api/services/useServices.ts b/src/core/api/services/useServices.ts index 7328e86..bf9a07c 100644 --- a/src/core/api/services/useServices.ts +++ b/src/core/api/services/useServices.ts @@ -1,35 +1,106 @@ import { useEffect } from "react"; import { useSelector, useDispatch } from "react-redux"; -import { getAllServices, getServicesIndex } from "./actions"; +import { stringify } from "qs"; +import { formatService } from "~core/utils"; -import { asyncState } from "~/types/asyncAction"; -import { formattedService } from "~/types/formattedService"; +import { ActionRequest, Request } from "~/types/services"; +import { axiosClient as client, DEFAULT_ERROR_MESSAGE } from "~/core/constants"; -export const useServices = (): formattedService[] => { +export const useServices = (): Request => { const services = useSelector(state => state.services); const dispatch = useDispatch(); useEffect(() => { if (services && services.data) return; - dispatch(getAllServices()); + dispatch(getAllServices); }, []); return services; }; -// This should pull the global google sheet and use that data -export const useServicesIndex = (): asyncState => { - const dispatch = useDispatch(); - const { loading, errorMessage, data } = useSelector( - state => state.servicesIndex - ); +const getAllServices = async (dispatch: Function) => { + dispatch(getServicesLoading()); - useEffect(() => { - if (data) return; - dispatch(getServicesIndex()); - }, []); + try { + const types = await getSheetTitles(); + const allServicesRes = await client.get("values:batchGet", { + params: { + majorDimension: "ROWS", + ranges: types, + }, + paramsSerializer: params => { + return stringify(params, { indices: false }); + }, + }); + + dispatch( + getServicesSuccess( + allServicesRes.data.valueRanges.reduce((list, type) => { + // Remove "Types, Id, etc..." row from service + type.values.shift(); + + return [...list, type.values.map(service => formatService(service))]; + }, []) + ) + ); + } catch (e) { + return dispatch(getServicesError(DEFAULT_ERROR_MESSAGE)); + } +}; - return { loading, errorMessage, data }; +const getSheetTitles = async () => { + // sheetMetadata will be a list of sheets: { "sheets": [ { "properties": { "title": "Index" } }, ... ] } + const sheetMetadata = await client.get("", { + params: { + fields: "sheets.properties.title", + }, + }); + return sheetMetadata.data.sheets + .map(sheet => sheet.properties.title) + .filter(title => title !== "Index"); }; +// Reducer that handles state for the useAPI hook +export const getServicesReducer = ( + state: Request = { loading: true, errorMessage: null, data: null }, + action: ActionRequest +) => { + switch (action.type) { + case "GET_SERVICES_LOADING": + return { ...state, loading: true, data: null, errorMessage: null }; + case "GET_SERVICES_ERROR": + return { + loading: false, + data: null, + errorMessage: action.errorMessage, + }; + case "GET_SERVICES_SUCCESS": + return { + loading: false, + data: action.payload, + errorMessage: null, + }; + default: + return state; + } +}; + +export const getServicesSuccess = (payload: any) => ({ + type: "GET_SERVICES_SUCCESS", + payload, + errorMessage: null, +}); + +export const getServicesError = (errorMessage: string) => ({ + type: "GET_SERVICES_ERROR", + payload: null, + errorMessage, +}); + +export const getServicesLoading = () => ({ + type: "GET_SERVICES_LOADING", + payload: null, + errorMessage: null, +}); + export default useServices; diff --git a/src/core/api/services/useServicesIndex.ts b/src/core/api/services/useServicesIndex.ts new file mode 100644 index 0000000..6a31830 --- /dev/null +++ b/src/core/api/services/useServicesIndex.ts @@ -0,0 +1,73 @@ +import { useEffect } from "react"; +import { useSelector, useDispatch } from "react-redux"; +import { getSheetData } from "~/core/utils"; + +import { ActionRequest, Request } from "~/types/services"; +import { + axiosClient as client, + DEFAULT_ERROR_MESSAGE, + INDEX_SHEET_TITLE, +} from "~/core/constants"; + +const useServicesIndex = (): Request => { + const servicesIndex = useSelector(state => state.servicesIndex); + const dispatch = useDispatch(); + + useEffect(() => { + if (servicesIndex && servicesIndex.data) return; + dispatch(getServicesIndex); + }, []); + + return servicesIndex; +}; + +const getServicesIndex = async (dispatch: Function) => { + dispatch(getServicesIndexLoading()); + + try { + const res = await getSheetByTitle(INDEX_SHEET_TITLE); + dispatch(getServicesIndexSuccess(getSheetData(res.data))); + } catch { + dispatch(getServicesIndexError(DEFAULT_ERROR_MESSAGE)); + } +}; + +const getSheetByTitle = async title => + await client.get("values:batchGet", { + params: { + majorDimension: "ROWS", + ranges: title, + }, + }); + +export const getServicesIndexReducer = ( + state: Request = { loading: true, errorMessage: null, data: null }, + action: ActionRequest +) => { + switch (action.type) { + case "GET_SHEET_INDEX_SUCCESS": + return { loading: false, data: action.payload, errorMessage: null }; + case "GET_SHEET_INDEX_ERROR": + return { loading: false, data: null, errorMessage: action.errorMessage }; + case "GET_SHEET_INDEX_LOADING": + return { loading: true, data: null, errorMessage: null }; + default: + return state; + } +}; + +export const getServicesIndexSuccess = (payload): ActionRequest => ({ + type: "GET_SHEET_INDEX_SUCCESS", + payload, +}); + +export const getServicesIndexError = (errorMessage): ActionRequest => ({ + type: "GET_SHEET_INDEX_ERROR", + errorMessage, +}); + +export const getServicesIndexLoading = (): ActionRequest => ({ + type: "GET_SHEET_INDEX_LOADING", +}); + +export default useServicesIndex; diff --git a/src/core/constants.ts b/src/core/constants.ts new file mode 100644 index 0000000..0ddbdc1 --- /dev/null +++ b/src/core/constants.ts @@ -0,0 +1,13 @@ +import axios from "axios"; + +export const API_KEY = process.env.GOOGLE_API_KEY; +export const DEFAULT_ERROR_MESSAGE = "Something went wrong!"; +export const INDEX_SHEET_TITLE = "Index"; +export const SHEET_ID = + process.env.SHEET_ID || "1ZPRRR8T51Tk-Co8h_GBh3G_7P2F7ZrYxPQDSYycpCUg"; + +// Create our API client and inject the API key into every request +export const axiosClient = axios.create({ + baseURL: `https://sheets.googleapis.com/v4/spreadsheets/${SHEET_ID}/`, + params: { key: API_KEY }, +}); diff --git a/src/core/store.ts b/src/core/store.ts index de55740..0e97aae 100644 --- a/src/core/store.ts +++ b/src/core/store.ts @@ -1,9 +1,7 @@ import { createStore, combineReducers, applyMiddleware } from "redux"; import thunk from "redux-thunk"; -import { - getServicesReducer as services, - getServicesIndex as servicesIndex, -} from "~core/api/services/reducers"; +import { getServicesReducer as services } from "~core/api/services/useServices"; +import { getServicesIndexReducer as servicesIndex } from "~core/api/services/useServicesIndex"; const reducers = combineReducers({ services, diff --git a/src/pages/categories/index.jsx b/src/pages/categories/index.jsx index e1c4eb3..a179a4b 100644 --- a/src/pages/categories/index.jsx +++ b/src/pages/categories/index.jsx @@ -1,5 +1,5 @@ import React, { Fragment } from "react"; -import { useServicesIndex } from "~/core/api/services/useServices"; +import useServicesIndex from "~/core/api/services/useServicesIndex"; import styled from "styled-components"; import Logo from "~/components/logo"; import Loader from "~/components/loader"; diff --git a/src/pages/service-detail/index.jsx b/src/pages/service-detail/index.jsx index 45ae376..307e309 100644 --- a/src/pages/service-detail/index.jsx +++ b/src/pages/service-detail/index.jsx @@ -9,8 +9,8 @@ import PhysicalInfo from "~/components/physical-info"; import Requirements from "~/components/requirements"; import TitleBar from "~/components/title-bar"; import { P1, P2 } from "~/components/typography"; -import api, { useAPI } from "~/core/api"; -import { formatPhoneNumber, formatService } from "~/core/utils"; +import useServices from "~/core/api/services/useServices"; +import { formatPhoneNumber } from "~/core/utils"; const ServiceCard = styled(Box)({ margin: "72px 16px 0 16px", @@ -32,8 +32,7 @@ const PhoneLink = styled.a({ const ServiceDetail = ({ match }) => { const { categoryId, serviceId, typeId } = match.params; - - const { loading, errorMessage, data } = useAPI(api.getServicesByType, typeId); + const { loading, errorMessage, data } = useServices(); if (loading) { return ; @@ -43,16 +42,14 @@ const ServiceDetail = ({ match }) => { return ; } - const serviceRow = data.find(d => d[1] === serviceId); + const typeList = data.find(typeList => typeList[1].type === typeId); + const service = typeList.find(service => service.id === serviceId); // If no service is found, show an empty state - if (!serviceRow) { + if (!service) { return

No service was found that matches this id!

; } - // Format the service into a useful object - const service = formatService(serviceRow); - return ( { const { categoryId, typeId } = match.params; - - const { loading, errorMessage, data } = useAPI(api.getServicesByType, typeId); + const { loading, errorMessage, data } = useServices(); if (loading) { return ; @@ -26,25 +24,21 @@ const Services = ({ match }) => { return ; } - // Format services into useful objects - const services = data.map(formatService); + // Get my services + const myServices = getMyServices(data, typeId); - // If there are no services, show an empty state - if (services.length === 0) { - return

No types were found for this category!

; + if (!myServices || myServices.length === 0) { + return

No services were found for this type!

; } - // Grab the current type from the first service - const currentType = services[0].type; - return ( - {services.map(s => ( + {myServices.map(s => ( { ); }; +const getMyServices = (allTypes, myType) => { + let myServices = []; + + allTypes.forEach(services => { + if (services[1].type === myType) { + myServices = services; + } + }); + + return myServices; +}; + export default Services; diff --git a/src/pages/types/index.jsx b/src/pages/types/index.jsx index 1c012e2..a3e0828 100644 --- a/src/pages/types/index.jsx +++ b/src/pages/types/index.jsx @@ -4,7 +4,7 @@ import Loader from "~/components/loader"; import Error from "~/components/error"; import TitleBar from "~/components/title-bar"; import TypeCard from "./type-card"; -import { useServicesIndex } from "~/core/api/services/useServices"; +import useServicesIndex from "~/core/api/services/useServicesIndex"; const TypesList = styled.ul({ listStyle: "none", diff --git a/src/types/asyncAction.ts b/src/types/asyncAction.ts deleted file mode 100644 index 176204d..0000000 --- a/src/types/asyncAction.ts +++ /dev/null @@ -1,11 +0,0 @@ -export interface asyncAction { - type: string; - payload: any; - errorMessage: string; -} - -export interface asyncState { - loading: boolean; - errorMessage: string; - data: any; -} diff --git a/src/types/formattedService.ts b/src/types/services/index.ts similarity index 60% rename from src/types/formattedService.ts rename to src/types/services/index.ts index f1b7023..546c74d 100644 --- a/src/types/formattedService.ts +++ b/src/types/services/index.ts @@ -1,4 +1,16 @@ -export interface formattedService { +export interface ActionRequest { + type: string; + errorMessage?: any; + payload?: any; +} + +export interface Request { + loading: boolean; + errorMessage: any; + data: any; +} + +export interface FormattedService { address: string; description: string; hours: number; @@ -17,5 +29,3 @@ export interface formattedService { lowIncome: boolean; }; } - -export default formattedService; From 27eda1ecb53ba97020ce22d934287fe42e090206 Mon Sep 17 00:00:00 2001 From: alex-cannon Date: Sun, 29 Dec 2019 13:16:20 -0700 Subject: [PATCH 13/19] chore: somehow fixed search. Not sure what I did. --- src/pages/search/index.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/search/index.jsx b/src/pages/search/index.jsx index df64a18..0be60a0 100644 --- a/src/pages/search/index.jsx +++ b/src/pages/search/index.jsx @@ -4,7 +4,7 @@ import Logo from "~/components/logo"; import Loader from "~/components/loader"; import InputAndSubmit from "~/components/inputAndSubmit"; import { H1 } from "~/components/typography"; -import { useSelector } from "react-redux"; +import api, { useAPI } from "~/core/api"; import ServiceCard from "~/pages/services/service-card"; import { formatService } from "~/core/utils"; @@ -58,8 +58,8 @@ const queryServices = (data, query) => { }; const Search = () => { - const { loading, error, data } = useSelector(state => state.services); - const index = { error: null }; + const { loading, error, data } = useAPI(api.getAllServices); + const index = useAPI(api.getIndex); const urlQuery = new URLSearchParams(location.search); const [searchValue, setSearchValue] = useState(urlQuery.get("s") || ""); From f7a1b923dc4ddcb57221a8f6f680a8ffd7d63102 Mon Sep 17 00:00:00 2001 From: alex-cannon Date: Fri, 3 Jan 2020 07:56:16 -0700 Subject: [PATCH 14/19] chore: fixed redux to be latest version --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 0bb191a..f4b1376 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "react-dom": "^16.8.6", "react-redux": "^7.1.3", "react-router-dom": "^5.0.0", - "redux": "^2.0.0 || ^3.0.0 || ^4.0.0-0", + "redux": "^4.0.5", "redux-thunk": "^2.3.0", "styled-components": "^4.2.0", "styled-normalize": "^8.0.6", diff --git a/yarn.lock b/yarn.lock index 4e4f5b3..39d96f7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6804,10 +6804,10 @@ redux-thunk@^2.3.0: resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622" integrity sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw== -"redux@^2.0.0 || ^3.0.0 || ^4.0.0-0": - version "4.0.4" - resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.4.tgz#4ee1aeb164b63d6a1bcc57ae4aa0b6e6fa7a3796" - integrity sha512-vKv4WdiJxOWKxK0yRoaK3Y4pxxB0ilzVx6dszU2W8wLxlb2yikRph4iV/ymtdJ6ZxpBLFbyrxklnT5yBbQSl3Q== +redux@^4.0.5: + version "4.0.5" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.5.tgz#4db5de5816e17891de8a80c424232d06f051d93f" + integrity sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w== dependencies: loose-envify "^1.4.0" symbol-observable "^1.2.0" From 7b48e1d7a1eb2942a98c8e7f0106b9551f3cca02 Mon Sep 17 00:00:00 2001 From: alex-cannon Date: Fri, 3 Jan 2020 07:58:52 -0700 Subject: [PATCH 15/19] chore: flipped order of conditional --- src/core/api/services/useServices.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/core/api/services/useServices.ts b/src/core/api/services/useServices.ts index bf9a07c..14269d0 100644 --- a/src/core/api/services/useServices.ts +++ b/src/core/api/services/useServices.ts @@ -11,8 +11,9 @@ export const useServices = (): Request => { const dispatch = useDispatch(); useEffect(() => { - if (services && services.data) return; - dispatch(getAllServices); + if (!services || !services.data) { + dispatch(getAllServices); + } }, []); return services; From 09385ef70a6a77610da7579caea7dc3d5f594aca Mon Sep 17 00:00:00 2001 From: alex-cannon Date: Sat, 4 Jan 2020 11:54:07 -0700 Subject: [PATCH 16/19] chore: Added strong type for getServicesSuccess --- src/core/api/services/useServices.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/api/services/useServices.ts b/src/core/api/services/useServices.ts index 14269d0..9f1eb32 100644 --- a/src/core/api/services/useServices.ts +++ b/src/core/api/services/useServices.ts @@ -3,7 +3,7 @@ import { useSelector, useDispatch } from "react-redux"; import { stringify } from "qs"; import { formatService } from "~core/utils"; -import { ActionRequest, Request } from "~/types/services"; +import { ActionRequest, Request, FormattedService } from "~/types/services"; import { axiosClient as client, DEFAULT_ERROR_MESSAGE } from "~/core/constants"; export const useServices = (): Request => { @@ -86,7 +86,7 @@ export const getServicesReducer = ( } }; -export const getServicesSuccess = (payload: any) => ({ +export const getServicesSuccess = (payload: FormattedService[][]) => ({ type: "GET_SERVICES_SUCCESS", payload, errorMessage: null, From bbc06ebdd0e7c2081f60b25ff761b959b9244d64 Mon Sep 17 00:00:00 2001 From: alex-cannon Date: Sat, 4 Jan 2020 11:58:23 -0700 Subject: [PATCH 17/19] feat: added strong type for google sheets coming in --- src/core/utils.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/core/utils.ts b/src/core/utils.ts index 5f73bfe..4f50fdd 100644 --- a/src/core/utils.ts +++ b/src/core/utils.ts @@ -1,7 +1,14 @@ import formattedService from "../types/formattedService"; -interface iSheet { - valueRanges: any[]; +export interface iSheet { + spreadsheetid: string; + valueRanges: valueRangeItem[]; +} + +export interface valueRangeItem { + range: string; + majorDimension: string; + values: string[]; } // Returns the rows of data from a sheet (excluding the header row) From 00c5734000a5400e81893b60c99bd5f913c10b52 Mon Sep 17 00:00:00 2001 From: alex-cannon Date: Sat, 4 Jan 2020 12:06:46 -0700 Subject: [PATCH 18/19] chore: alphabetized imports for category components --- src/core/utils.ts | 6 +++--- src/index.tsx | 2 +- src/pages/categories/index.jsx | 12 ++++++------ src/pages/service-detail/index.jsx | 10 +++++----- src/pages/services/index.jsx | 6 +++--- src/pages/types/index.jsx | 4 ++-- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/core/utils.ts b/src/core/utils.ts index 4f50fdd..9bb5c73 100644 --- a/src/core/utils.ts +++ b/src/core/utils.ts @@ -1,4 +1,4 @@ -import formattedService from "../types/formattedService"; +import { FormattedService } from "~/types/services"; export interface iSheet { spreadsheetid: string; @@ -12,7 +12,7 @@ export interface valueRangeItem { } // Returns the rows of data from a sheet (excluding the header row) -export const getSheetData = (sheet: iSheet): formattedService[] => { +export const getSheetData = (sheet: iSheet): string[] => { const items = sheet.valueRanges[0].values; // Remove the header row items.shift(); @@ -25,7 +25,7 @@ export const getSheetData = (sheet: iSheet): formattedService[] => { * Note: A fallback is required for the Phone # field because * the Sheets API omits the last column if there's no value. */ -export const formatService = (service: any): formattedService => { +export const formatService = (service: any): FormattedService => { // Split the comma separated populations into an array const populations = service[3] === "" ? [] : service[3].split(", "); return { diff --git a/src/index.tsx b/src/index.tsx index fb4dd30..52e7345 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -4,7 +4,7 @@ import { BrowserRouter, Redirect, Route, Switch } from "react-router-dom"; import { Provider } from "react-redux"; import styled, { createGlobalStyle, ThemeProvider } from "styled-components"; import { Normalize } from "styled-normalize"; -import { initializeStore } from "~core/store"; +import { initializeStore } from "~/core/store"; import Nav from "~/components/nav"; import theme from "~/core/theme"; import Categories from "~/pages/categories"; diff --git a/src/pages/categories/index.jsx b/src/pages/categories/index.jsx index a179a4b..efed5ec 100644 --- a/src/pages/categories/index.jsx +++ b/src/pages/categories/index.jsx @@ -1,11 +1,11 @@ -import React, { Fragment } from "react"; -import useServicesIndex from "~/core/api/services/useServicesIndex"; -import styled from "styled-components"; -import Logo from "~/components/logo"; -import Loader from "~/components/loader"; +import CategoryCard from "./category-card"; import Error from "~/components/error"; import { H1 } from "~/components/typography"; -import CategoryCard from "./category-card"; +import Logo from "~/components/logo"; +import Loader from "~/components/loader"; +import React, { Fragment } from "react"; +import styled from "styled-components"; +import useServicesIndex from "~/core/api/services/useServicesIndex"; const StyledLogo = styled(Logo)({ margin: "48px auto", diff --git a/src/pages/service-detail/index.jsx b/src/pages/service-detail/index.jsx index 307e309..bc96631 100644 --- a/src/pages/service-detail/index.jsx +++ b/src/pages/service-detail/index.jsx @@ -1,16 +1,16 @@ -import React, { Fragment } from "react"; -import styled from "styled-components"; import Box from "~/components/box"; import Button from "~/components/button"; import Divider from "~/components/divider"; -import Loader from "~/components/loader"; import Error from "~/components/error"; +import { formatPhoneNumber } from "~/core/utils"; +import Loader from "~/components/loader"; import PhysicalInfo from "~/components/physical-info"; +import { P1, P2 } from "~/components/typography"; +import React, { Fragment } from "react"; import Requirements from "~/components/requirements"; +import styled from "styled-components"; import TitleBar from "~/components/title-bar"; -import { P1, P2 } from "~/components/typography"; import useServices from "~/core/api/services/useServices"; -import { formatPhoneNumber } from "~/core/utils"; const ServiceCard = styled(Box)({ margin: "72px 16px 0 16px", diff --git a/src/pages/services/index.jsx b/src/pages/services/index.jsx index c94b1af..bb08ace 100644 --- a/src/pages/services/index.jsx +++ b/src/pages/services/index.jsx @@ -1,10 +1,10 @@ +import Error from "~/components/error"; +import Loader from "~/components/loader"; import React, { Fragment } from "react"; +import ServiceCard from "~/pages/services/service-card"; import styled from "styled-components"; -import Loader from "~/components/loader"; -import Error from "~/components/error"; import TitleBar from "~/components/title-bar"; import useServices from "~/core/api/services/useServices"; -import ServiceCard from "~/pages/services/service-card"; const ServicesList = styled.ul({ listStyle: "none", diff --git a/src/pages/types/index.jsx b/src/pages/types/index.jsx index a3e0828..1ea008b 100644 --- a/src/pages/types/index.jsx +++ b/src/pages/types/index.jsx @@ -1,7 +1,7 @@ +import Error from "~/components/error"; +import Loader from "~/components/loader"; import React, { Fragment } from "react"; import styled from "styled-components"; -import Loader from "~/components/loader"; -import Error from "~/components/error"; import TitleBar from "~/components/title-bar"; import TypeCard from "./type-card"; import useServicesIndex from "~/core/api/services/useServicesIndex"; From 072a9f8b480cb54ff38a1f2ab05a0e9f70b84a53 Mon Sep 17 00:00:00 2001 From: alex-cannon Date: Sat, 4 Jan 2020 12:08:30 -0700 Subject: [PATCH 19/19] chore: removed not data from categories render --- src/pages/categories/index.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/categories/index.jsx b/src/pages/categories/index.jsx index efed5ec..1b45eba 100644 --- a/src/pages/categories/index.jsx +++ b/src/pages/categories/index.jsx @@ -49,7 +49,7 @@ const Categories = () => { Pick a category to see services in your area. - {loading || !data ? ( + {loading ? ( ) : (