summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRenaud Chaput <renchap@gmail.com>2023-09-11 16:09:22 +0200
committerGitHub <noreply@github.com>2023-09-11 16:09:22 +0200
commitbd06c13204b13818cb2d7695d9af25fe813fcdb5 (patch)
tree4e7bced0c77f6d688dc235f4018580a8e0c2128c
parent7730083611b345e14e14f8e31ddbb124ff8cc7fd (diff)
Convert `actions/account_notes` into Typescript (#26601)
-rw-r--r--app/javascript/mastodon/actions/account_notes.js37
-rw-r--r--app/javascript/mastodon/actions/account_notes.ts18
-rw-r--r--app/javascript/mastodon/api.js76
-rw-r--r--app/javascript/mastodon/api.ts63
-rw-r--r--app/javascript/mastodon/features/account/containers/account_note_container.js2
-rw-r--r--app/javascript/mastodon/reducers/relationships.js6
-rw-r--r--app/javascript/mastodon/store/index.ts53
-rw-r--r--app/javascript/mastodon/store/store.ts40
-rw-r--r--app/javascript/mastodon/store/typed_functions.ts16
9 files changed, 149 insertions, 162 deletions
diff --git a/app/javascript/mastodon/actions/account_notes.js b/app/javascript/mastodon/actions/account_notes.js
deleted file mode 100644
index 72b943300d8..00000000000
--- a/app/javascript/mastodon/actions/account_notes.js
+++ /dev/null
@@ -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,
- };
-}
diff --git a/app/javascript/mastodon/actions/account_notes.ts b/app/javascript/mastodon/actions/account_notes.ts
new file mode 100644
index 00000000000..eeef23e3666
--- /dev/null
+++ b/app/javascript/mastodon/actions/account_notes.ts
@@ -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 };
+ },
+);
diff --git a/app/javascript/mastodon/api.js b/app/javascript/mastodon/api.js
deleted file mode 100644
index 1c171a1c4aa..00000000000
--- a/app/javascript/mastodon/api.js
+++ /dev/null
@@ -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;
- }
- },
- ],
- });
-}
diff --git a/app/javascript/mastodon/api.ts b/app/javascript/mastodon/api.ts
new file mode 100644
index 00000000000..f262fd85707
--- /dev/null
+++ b/app/javascript/mastodon/api.ts
@@ -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;
+ }
+ },
+ ],
+ });
+}
diff --git a/app/javascript/mastodon/features/account/containers/account_note_container.js b/app/javascript/mastodon/features/account/containers/account_note_container.js
index 9fbe0671c06..20304a45243 100644
--- a/app/javascript/mastodon/features/account/containers/account_note_container.js
+++ b/app/javascript/mastodon/features/account/containers/account_note_container.js
@@ -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}));
},
});
diff --git a/app/javascript/mastodon/reducers/relationships.js b/app/javascript/mastodon/reducers/relationships.js
index d1ccf9ac953..191448c0e8f 100644
--- a/app/javascript/mastodon/reducers/relationships.js
+++ b/app/javascript/mastodon/reducers/relationships.js
@@ -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:
diff --git a/app/javascript/mastodon/store/index.ts b/app/javascript/mastodon/store/index.ts
index f7486627940..c2629b0ed74 100644
--- a/app/javascript/mastodon/store/index.ts
+++ b/app/javascript/mastodon/store/index.ts
@@ -1,45 +1,8 @@
-import type { TypedUseSelectorHook } from 'react-redux';
-import { useDispatch, useSelector } from 'react-redux';
-
-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 { store } from './store';
+export type { GetState, AppDispatch, RootState } from './store';
+
+export {
+ createAppAsyncThunk,
+ useAppDispatch,
+ useAppSelector,
+} from './typed_functions';
diff --git a/app/javascript/mastodon/store/store.ts b/app/javascript/mastodon/store/store.ts
new file mode 100644
index 00000000000..63508856803
--- /dev/null
+++ b/app/javascript/mastodon/store/store.ts
@@ -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;
diff --git a/app/javascript/mastodon/store/typed_functions.ts b/app/javascript/mastodon/store/typed_functions.ts
new file mode 100644
index 00000000000..d05a256baba
--- /dev/null
+++ b/app/javascript/mastodon/store/typed_functions.ts
@@ -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 };
+}>();