Convert actions/account_notes into Typescript (#26601)
		
	This commit is contained in:
		
					parent
					
						
							
								7730083611
							
						
					
				
			
			
				commit
				
					
						bd06c13204
					
				
			
		
					 9 changed files with 148 additions and 161 deletions
				
			
		|  | @ -1,37 +0,0 @@ | |||
| import api from '../api'; | ||||
| 
 | ||||
| export const ACCOUNT_NOTE_SUBMIT_REQUEST = 'ACCOUNT_NOTE_SUBMIT_REQUEST'; | ||||
| export const ACCOUNT_NOTE_SUBMIT_SUCCESS = 'ACCOUNT_NOTE_SUBMIT_SUCCESS'; | ||||
| export const ACCOUNT_NOTE_SUBMIT_FAIL    = 'ACCOUNT_NOTE_SUBMIT_FAIL'; | ||||
| 
 | ||||
| export function submitAccountNote(id, value) { | ||||
|   return (dispatch, getState) => { | ||||
|     dispatch(submitAccountNoteRequest()); | ||||
| 
 | ||||
|     api(getState).post(`/api/v1/accounts/${id}/note`, { | ||||
|       comment: value, | ||||
|     }).then(response => { | ||||
|       dispatch(submitAccountNoteSuccess(response.data)); | ||||
|     }).catch(error => dispatch(submitAccountNoteFail(error))); | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| export function submitAccountNoteRequest() { | ||||
|   return { | ||||
|     type: ACCOUNT_NOTE_SUBMIT_REQUEST, | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| export function submitAccountNoteSuccess(relationship) { | ||||
|   return { | ||||
|     type: ACCOUNT_NOTE_SUBMIT_SUCCESS, | ||||
|     relationship, | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| export function submitAccountNoteFail(error) { | ||||
|   return { | ||||
|     type: ACCOUNT_NOTE_SUBMIT_FAIL, | ||||
|     error, | ||||
|   }; | ||||
| } | ||||
							
								
								
									
										18
									
								
								app/javascript/mastodon/actions/account_notes.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								app/javascript/mastodon/actions/account_notes.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,18 @@ | |||
| import { createAppAsyncThunk } from 'mastodon/store/typed_functions'; | ||||
| 
 | ||||
| import api from '../api'; | ||||
| 
 | ||||
| export const submitAccountNote = createAppAsyncThunk( | ||||
|   'account_note/submit', | ||||
|   async (args: { id: string; value: string }, { getState }) => { | ||||
|     // TODO: replace `unknown` with `ApiRelationshipJSON` when it is merged
 | ||||
|     const response = await api(getState).post<unknown>( | ||||
|       `/api/v1/accounts/${args.id}/note`, | ||||
|       { | ||||
|         comment: args.value, | ||||
|       }, | ||||
|     ); | ||||
| 
 | ||||
|     return { relationship: response.data }; | ||||
|   }, | ||||
| ); | ||||
|  | @ -1,76 +0,0 @@ | |||
| // @ts-check
 | ||||
| 
 | ||||
| import axios from 'axios'; | ||||
| import LinkHeader from 'http-link-header'; | ||||
| 
 | ||||
| import ready from './ready'; | ||||
| 
 | ||||
| /** | ||||
|  * @param {import('axios').AxiosResponse} response | ||||
|  * @returns {LinkHeader} | ||||
|  */ | ||||
| export const getLinks = response => { | ||||
|   const value = response.headers.link; | ||||
| 
 | ||||
|   if (!value) { | ||||
|     return new LinkHeader(); | ||||
|   } | ||||
| 
 | ||||
|   return LinkHeader.parse(value); | ||||
| }; | ||||
| 
 | ||||
| /** @type {import('axios').RawAxiosRequestHeaders} */ | ||||
| const csrfHeader = {}; | ||||
| 
 | ||||
| /** | ||||
|  * @returns {void} | ||||
|  */ | ||||
| const setCSRFHeader = () => { | ||||
|   /** @type {HTMLMetaElement | null} */ | ||||
|   const csrfToken = document.querySelector('meta[name=csrf-token]'); | ||||
| 
 | ||||
|   if (csrfToken) { | ||||
|     csrfHeader['X-CSRF-Token'] = csrfToken.content; | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| ready(setCSRFHeader); | ||||
| 
 | ||||
| /** | ||||
|  * @param {() => import('immutable').Map<string,any>} getState | ||||
|  * @returns {import('axios').RawAxiosRequestHeaders} | ||||
|  */ | ||||
| const authorizationHeaderFromState = getState => { | ||||
|   const accessToken = getState && getState().getIn(['meta', 'access_token'], ''); | ||||
| 
 | ||||
|   if (!accessToken) { | ||||
|     return {}; | ||||
|   } | ||||
| 
 | ||||
|   return { | ||||
|     'Authorization': `Bearer ${accessToken}`, | ||||
|   }; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * @param {() => import('immutable').Map<string,any>} getState | ||||
|  * @returns {import('axios').AxiosInstance} | ||||
|  */ | ||||
| export default function api(getState) { | ||||
|   return axios.create({ | ||||
|     headers: { | ||||
|       ...csrfHeader, | ||||
|       ...authorizationHeaderFromState(getState), | ||||
|     }, | ||||
| 
 | ||||
|     transformResponse: [ | ||||
|       function (data) { | ||||
|         try { | ||||
|           return JSON.parse(data); | ||||
|         } catch { | ||||
|           return data; | ||||
|         } | ||||
|       }, | ||||
|     ], | ||||
|   }); | ||||
| } | ||||
							
								
								
									
										63
									
								
								app/javascript/mastodon/api.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								app/javascript/mastodon/api.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,63 @@ | |||
| import type { AxiosResponse, RawAxiosRequestHeaders } from 'axios'; | ||||
| import axios from 'axios'; | ||||
| import LinkHeader from 'http-link-header'; | ||||
| 
 | ||||
| import ready from './ready'; | ||||
| import type { GetState } from './store'; | ||||
| 
 | ||||
| export const getLinks = (response: AxiosResponse) => { | ||||
|   const value = response.headers.link as string | undefined; | ||||
| 
 | ||||
|   if (!value) { | ||||
|     return new LinkHeader(); | ||||
|   } | ||||
| 
 | ||||
|   return LinkHeader.parse(value); | ||||
| }; | ||||
| 
 | ||||
| const csrfHeader: RawAxiosRequestHeaders = {}; | ||||
| 
 | ||||
| const setCSRFHeader = () => { | ||||
|   const csrfToken = document.querySelector<HTMLMetaElement>( | ||||
|     'meta[name=csrf-token]', | ||||
|   ); | ||||
| 
 | ||||
|   if (csrfToken) { | ||||
|     csrfHeader['X-CSRF-Token'] = csrfToken.content; | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| void ready(setCSRFHeader); | ||||
| 
 | ||||
| const authorizationHeaderFromState = (getState?: GetState) => { | ||||
|   const accessToken = | ||||
|     getState && (getState().meta.get('access_token', '') as string); | ||||
| 
 | ||||
|   if (!accessToken) { | ||||
|     return {}; | ||||
|   } | ||||
| 
 | ||||
|   return { | ||||
|     Authorization: `Bearer ${accessToken}`, | ||||
|   } as RawAxiosRequestHeaders; | ||||
| }; | ||||
| 
 | ||||
| // eslint-disable-next-line import/no-default-export
 | ||||
| export default function api(getState: GetState) { | ||||
|   return axios.create({ | ||||
|     headers: { | ||||
|       ...csrfHeader, | ||||
|       ...authorizationHeaderFromState(getState), | ||||
|     }, | ||||
| 
 | ||||
|     transformResponse: [ | ||||
|       function (data: unknown) { | ||||
|         try { | ||||
|           return JSON.parse(data as string) as unknown; | ||||
|         } catch { | ||||
|           return data; | ||||
|         } | ||||
|       }, | ||||
|     ], | ||||
|   }); | ||||
| } | ||||
|  | @ -11,7 +11,7 @@ const mapStateToProps = (state, { account }) => ({ | |||
| const mapDispatchToProps = (dispatch, { account }) => ({ | ||||
| 
 | ||||
|   onSave (value) { | ||||
|     dispatch(submitAccountNote(account.get('id'), value)); | ||||
|     dispatch(submitAccountNote({ id: account.get('id'), value})); | ||||
|   }, | ||||
| 
 | ||||
| }); | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| import { Map as ImmutableMap, fromJS } from 'immutable'; | ||||
| 
 | ||||
| import { | ||||
|   ACCOUNT_NOTE_SUBMIT_SUCCESS, | ||||
|   submitAccountNote, | ||||
| } from '../actions/account_notes'; | ||||
| import { | ||||
|   ACCOUNT_FOLLOW_SUCCESS, | ||||
|  | @ -73,10 +73,10 @@ export default function relationships(state = initialState, action) { | |||
|   case ACCOUNT_UNMUTE_SUCCESS: | ||||
|   case ACCOUNT_PIN_SUCCESS: | ||||
|   case ACCOUNT_UNPIN_SUCCESS: | ||||
|   case ACCOUNT_NOTE_SUBMIT_SUCCESS: | ||||
|     return normalizeRelationship(state, action.relationship); | ||||
|   case RELATIONSHIPS_FETCH_SUCCESS: | ||||
|     return normalizeRelationships(state, action.relationships); | ||||
|   case submitAccountNote.fulfilled: | ||||
|     return normalizeRelationship(state, action.payload.relationship); | ||||
|   case DOMAIN_BLOCK_SUCCESS: | ||||
|     return setDomainBlocking(state, action.accounts, true); | ||||
|   case DOMAIN_UNBLOCK_SUCCESS: | ||||
|  |  | |||
|  | @ -1,45 +1,8 @@ | |||
| import type { TypedUseSelectorHook } from 'react-redux'; | ||||
| import { useDispatch, useSelector } from 'react-redux'; | ||||
| export { store } from './store'; | ||||
| export type { GetState, AppDispatch, RootState } from './store'; | ||||
| 
 | ||||
| import { configureStore } from '@reduxjs/toolkit'; | ||||
| 
 | ||||
| import { rootReducer } from '../reducers'; | ||||
| 
 | ||||
| import { errorsMiddleware } from './middlewares/errors'; | ||||
| import { loadingBarMiddleware } from './middlewares/loading_bar'; | ||||
| import { soundsMiddleware } from './middlewares/sounds'; | ||||
| 
 | ||||
| export const store = configureStore({ | ||||
|   reducer: rootReducer, | ||||
|   middleware: (getDefaultMiddleware) => | ||||
|     getDefaultMiddleware({ | ||||
|       // In development, Redux Toolkit enables 2 default middlewares to detect
 | ||||
|       // common issues with states. Unfortunately, our use of ImmutableJS for state
 | ||||
|       // triggers both, so lets disable them until our state is fully refactored
 | ||||
| 
 | ||||
|       // https://redux-toolkit.js.org/api/serializabilityMiddleware
 | ||||
|       // This checks recursively that every values in the state are serializable in JSON
 | ||||
|       // Which is not the case, as we use ImmutableJS structures, but also File objects
 | ||||
|       serializableCheck: false, | ||||
| 
 | ||||
|       // https://redux-toolkit.js.org/api/immutabilityMiddleware
 | ||||
|       // This checks recursively if every value in the state is immutable (ie, a JS primitive type)
 | ||||
|       // But this is not the case, as our Root State is an ImmutableJS map, which is an object
 | ||||
|       immutableCheck: false, | ||||
|     }) | ||||
|       .concat( | ||||
|         loadingBarMiddleware({ | ||||
|           promiseTypeSuffixes: ['REQUEST', 'SUCCESS', 'FAIL'], | ||||
|         }), | ||||
|       ) | ||||
|       .concat(errorsMiddleware) | ||||
|       .concat(soundsMiddleware()), | ||||
| }); | ||||
| 
 | ||||
| // Infer the `RootState` and `AppDispatch` types from the store itself
 | ||||
| export type RootState = ReturnType<typeof rootReducer>; | ||||
| // Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
 | ||||
| export type AppDispatch = typeof store.dispatch; | ||||
| 
 | ||||
| export const useAppDispatch: () => AppDispatch = useDispatch; | ||||
| export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector; | ||||
| export { | ||||
|   createAppAsyncThunk, | ||||
|   useAppDispatch, | ||||
|   useAppSelector, | ||||
| } from './typed_functions'; | ||||
|  |  | |||
							
								
								
									
										40
									
								
								app/javascript/mastodon/store/store.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								app/javascript/mastodon/store/store.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,40 @@ | |||
| import { configureStore } from '@reduxjs/toolkit'; | ||||
| 
 | ||||
| import { rootReducer } from '../reducers'; | ||||
| 
 | ||||
| import { errorsMiddleware } from './middlewares/errors'; | ||||
| import { loadingBarMiddleware } from './middlewares/loading_bar'; | ||||
| import { soundsMiddleware } from './middlewares/sounds'; | ||||
| 
 | ||||
| export const store = configureStore({ | ||||
|   reducer: rootReducer, | ||||
|   middleware: (getDefaultMiddleware) => | ||||
|     getDefaultMiddleware({ | ||||
|       // In development, Redux Toolkit enables 2 default middlewares to detect
 | ||||
|       // common issues with states. Unfortunately, our use of ImmutableJS for state
 | ||||
|       // triggers both, so lets disable them until our state is fully refactored
 | ||||
| 
 | ||||
|       // https://redux-toolkit.js.org/api/serializabilityMiddleware
 | ||||
|       // This checks recursively that every values in the state are serializable in JSON
 | ||||
|       // Which is not the case, as we use ImmutableJS structures, but also File objects
 | ||||
|       serializableCheck: false, | ||||
| 
 | ||||
|       // https://redux-toolkit.js.org/api/immutabilityMiddleware
 | ||||
|       // This checks recursively if every value in the state is immutable (ie, a JS primitive type)
 | ||||
|       // But this is not the case, as our Root State is an ImmutableJS map, which is an object
 | ||||
|       immutableCheck: false, | ||||
|     }) | ||||
|       .concat( | ||||
|         loadingBarMiddleware({ | ||||
|           promiseTypeSuffixes: ['REQUEST', 'SUCCESS', 'FAIL'], | ||||
|         }), | ||||
|       ) | ||||
|       .concat(errorsMiddleware) | ||||
|       .concat(soundsMiddleware()), | ||||
| }); | ||||
| 
 | ||||
| // Infer the `RootState` and `AppDispatch` types from the store itself
 | ||||
| export type RootState = ReturnType<typeof rootReducer>; | ||||
| // Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
 | ||||
| export type AppDispatch = typeof store.dispatch; | ||||
| export type GetState = typeof store.getState; | ||||
							
								
								
									
										16
									
								
								app/javascript/mastodon/store/typed_functions.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								app/javascript/mastodon/store/typed_functions.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,16 @@ | |||
| import type { TypedUseSelectorHook } from 'react-redux'; | ||||
| import { useDispatch, useSelector } from 'react-redux'; | ||||
| 
 | ||||
| import { createAsyncThunk } from '@reduxjs/toolkit'; | ||||
| 
 | ||||
| import type { AppDispatch, RootState } from './store'; | ||||
| 
 | ||||
| export const useAppDispatch: () => AppDispatch = useDispatch; | ||||
| export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector; | ||||
| 
 | ||||
| export const createAppAsyncThunk = createAsyncThunk.withTypes<{ | ||||
|   state: RootState; | ||||
|   dispatch: AppDispatch; | ||||
|   rejectValue: string; | ||||
|   extra: { s: string; n: number }; | ||||
| }>(); | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue