-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix TypeScript Errors and add Redux #64
Changes from all commits
166a07a
085976b
61b4dc7
54bb23e
8b06a93
7a98ff6
7344f02
82db9ea
2e608ed
215ce9b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,7 +2,7 @@ | |
"name": "fd-servicedirectory", | ||
"version": "1.0.0", | ||
"description": "PFA Service Directory", | ||
"main": "src/index.jsx", | ||
"main": "src/index.tsx", | ||
"repository": "[email protected]:CodeForFoco/fd-servicedirectory.git", | ||
"author": "Code for Fort Collins", | ||
"license": "MIT", | ||
|
@@ -49,14 +49,18 @@ | |
}, | ||
"dependencies": { | ||
"@types/jest": "^24.0.23", | ||
"@types/node": "^12.12.11", | ||
"axios": "0.18.1", | ||
"husky": "^2.4.0", | ||
"lodash": "^4.17.13", | ||
"polished": "^3.3.0", | ||
"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": "^4.0.4", | ||
"redux-thunk": "^2.3.0", | ||
"styled-components": "^4.2.0", | ||
"styled-normalize": "^8.0.6", | ||
"ts-jest": "^24.1.0" | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
export interface asyncAction { | ||
type: string; | ||
payload: any; | ||
errorMessage: string; | ||
} | ||
|
||
export interface asyncState { | ||
loading: boolean; | ||
errorMessage: string; | ||
data: any; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
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 = 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)); | ||
}; | ||
|
||
// 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, | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import { asyncState, asyncAction } from "~/core/interfaces/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: 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; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
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 = () => { | ||
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()); | ||
}, []); | ||
|
||
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 }; | ||
}; | ||
|
||
/** | ||
* 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 => {}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are these being used? If not could we get rid of them for now? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah for sure. I think for now, I should remove any code like this or that isn't being used. The app doesn't use redux yet, but I think it'd be good to merge in TypeScript for now. |
||
|
||
// Returns a list of services for a subtype | ||
//export const useSubTypeServices = subtype => {}; | ||
|
||
export default useServices; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { createStore, combineReducers, applyMiddleware } from "redux"; | ||
import thunk from "redux-thunk"; | ||
import { | ||
getServicesReducer as services, | ||
getServicesIndex as servicesIndex, | ||
} from "~/core/store/services/reducers"; | ||
|
||
const reducers = combineReducers({ | ||
services, | ||
servicesIndex, | ||
}); | ||
|
||
export const configureStore = initialState => { | ||
return createStore(reducers, initialState, applyMiddleware(thunk)); | ||
}; | ||
|
||
export const initializeStore = () => { | ||
// State is intialized per reducer | ||
return configureStore({}); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm curious, what do we need Node types for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this was an issue to do with making TypeScript recognize
process.env
as a global. I should look into it as it's been a few weeks.