From 708c3a9e64a97a248c4dfe7e4e0179f24e081a55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Fri, 10 Jun 2022 19:56:22 +0200 Subject: [PATCH] TypeScript: actions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- app/soapbox/actions/{apps.js => apps.ts} | 12 +- app/soapbox/actions/{auth.js => auth.ts} | 214 ++++++++---------- .../actions/{backups.js => backups.ts} | 24 +- .../{external_auth.js => external_auth.ts} | 71 +++--- app/soapbox/actions/{oauth.js => oauth.ts} | 12 +- app/soapbox/api.ts | 2 +- app/soapbox/components/birthday_input.tsx | 2 +- app/soapbox/components/sidebar-navigation.tsx | 2 +- app/soapbox/components/sidebar_menu.tsx | 2 +- app/soapbox/components/thumb_navigation.tsx | 2 +- .../auth_login/components/captcha.tsx | 4 +- .../auth_login/components/login_page.tsx | 6 +- .../features/auth_login/components/logout.tsx | 4 +- .../auth_login/components/otp_auth_form.tsx | 4 +- app/soapbox/features/backups/index.tsx | 5 +- .../features/chats/components/chat.tsx | 2 +- .../features/chats/components/chat_list.tsx | 6 +- .../public_layout/components/header.tsx | 2 +- .../features/ui/components/link_footer.tsx | 5 +- app/soapbox/features/ui/components/navbar.tsx | 2 +- .../ui/components/profile-dropdown.tsx | 2 +- .../features/verification/registration.tsx | 2 +- .../features/verification/waitlist_page.tsx | 4 +- app/soapbox/hooks/useAppDispatch.ts | 2 +- app/soapbox/normalizers/instance.ts | 1 + app/soapbox/selectors/index.ts | 4 +- app/soapbox/utils/auth.ts | 4 +- 27 files changed, 188 insertions(+), 214 deletions(-) rename app/soapbox/actions/{apps.js => apps.ts} (80%) rename app/soapbox/actions/{auth.js => auth.ts} (55%) rename app/soapbox/actions/{backups.js => backups.ts} (63%) rename app/soapbox/actions/{external_auth.js => external_auth.ts} (63%) rename app/soapbox/actions/{oauth.js => oauth.ts} (83%) diff --git a/app/soapbox/actions/apps.js b/app/soapbox/actions/apps.ts similarity index 80% rename from app/soapbox/actions/apps.js rename to app/soapbox/actions/apps.ts index 7eb00c98c..d2ef2ff0d 100644 --- a/app/soapbox/actions/apps.js +++ b/app/soapbox/actions/apps.ts @@ -8,6 +8,8 @@ import { baseClient } from '../api'; +import type { AnyAction } from 'redux'; + export const APP_CREATE_REQUEST = 'APP_CREATE_REQUEST'; export const APP_CREATE_SUCCESS = 'APP_CREATE_SUCCESS'; export const APP_CREATE_FAIL = 'APP_CREATE_FAIL'; @@ -16,12 +18,12 @@ export const APP_VERIFY_CREDENTIALS_REQUEST = 'APP_VERIFY_CREDENTIALS_REQUEST'; export const APP_VERIFY_CREDENTIALS_SUCCESS = 'APP_VERIFY_CREDENTIALS_SUCCESS'; export const APP_VERIFY_CREDENTIALS_FAIL = 'APP_VERIFY_CREDENTIALS_FAIL'; -export function createApp(params, baseURL) { - return (dispatch, getState) => { +export function createApp(params?: Record, baseURL?: string) { + return (dispatch: React.Dispatch) => { dispatch({ type: APP_CREATE_REQUEST, params }); return baseClient(null, baseURL).post('/api/v1/apps', params).then(({ data: app }) => { dispatch({ type: APP_CREATE_SUCCESS, params, app }); - return app; + return app as Record; }).catch(error => { dispatch({ type: APP_CREATE_FAIL, params, error }); throw error; @@ -29,8 +31,8 @@ export function createApp(params, baseURL) { }; } -export function verifyAppCredentials(token) { - return (dispatch, getState) => { +export function verifyAppCredentials(token: string) { + return (dispatch: React.Dispatch) => { dispatch({ type: APP_VERIFY_CREDENTIALS_REQUEST, token }); return baseClient(token).get('/api/v1/apps/verify_credentials').then(({ data: app }) => { dispatch({ type: APP_VERIFY_CREDENTIALS_SUCCESS, token, app }); diff --git a/app/soapbox/actions/auth.js b/app/soapbox/actions/auth.ts similarity index 55% rename from app/soapbox/actions/auth.js rename to app/soapbox/actions/auth.ts index a42bccdef..49de0e475 100644 --- a/app/soapbox/actions/auth.js +++ b/app/soapbox/actions/auth.ts @@ -26,6 +26,10 @@ import api, { baseClient } from '../api'; import { importFetchedAccount } from './importer'; +import type { AxiosError } from 'axios'; +import type { Map as ImmutableMap } from 'immutable'; +import type { AppDispatch, RootState } from 'soapbox/store'; + export const SWITCH_ACCOUNT = 'SWITCH_ACCOUNT'; export const AUTH_APP_CREATED = 'AUTH_APP_CREATED'; @@ -48,35 +52,32 @@ export const messages = defineMessages({ invalidCredentials: { id: 'auth.invalid_credentials', defaultMessage: 'Wrong username or password' }, }); -const noOp = () => new Promise(f => f()); +const noOp = () => new Promise(f => f(undefined)); -const getScopes = state => { - const instance = state.get('instance'); +const getScopes = (state: RootState) => { + const instance = state.instance; const { scopes } = getFeatures(instance); return scopes; }; -function createAppAndToken() { - return (dispatch, getState) => { - return dispatch(getAuthApp()).then(() => { - return dispatch(createAppToken()); - }); - }; -} +const createAppAndToken = () => + (dispatch: AppDispatch) => + dispatch(getAuthApp()).then(() => + dispatch(createAppToken()), + ); /** Create an auth app, or use it from build config */ -function getAuthApp() { - return (dispatch, getState) => { +const getAuthApp = () => + (dispatch: AppDispatch) => { if (customApp?.client_secret) { return noOp().then(() => dispatch({ type: AUTH_APP_CREATED, app: customApp })); } else { return dispatch(createAuthApp()); } }; -} -function createAuthApp() { - return (dispatch, getState) => { +const createAuthApp = () => + (dispatch: AppDispatch, getState: () => any) => { const params = { client_name: sourceCode.displayName, redirect_uris: 'urn:ietf:wg:oauth:2.0:oob', @@ -84,15 +85,14 @@ function createAuthApp() { website: sourceCode.homepage, }; - return dispatch(createApp(params)).then(app => { - return dispatch({ type: AUTH_APP_CREATED, app }); - }); + return dispatch(createApp(params)).then((app: Record) => + dispatch({ type: AUTH_APP_CREATED, app }), + ); }; -} -function createAppToken() { - return (dispatch, getState) => { - const app = getState().getIn(['auth', 'app']); +const createAppToken = () => + (dispatch: AppDispatch, getState: () => any) => { + const app = getState().auth.get('app'); const params = { client_id: app.get('client_id'), @@ -102,15 +102,14 @@ function createAppToken() { scope: getScopes(getState()), }; - return dispatch(obtainOAuthToken(params)).then(token => { - return dispatch({ type: AUTH_APP_AUTHORIZED, app, token }); - }); + return dispatch(obtainOAuthToken(params)).then((token: Record) => + dispatch({ type: AUTH_APP_AUTHORIZED, app, token }), + ); }; -} -function createUserToken(username, password) { - return (dispatch, getState) => { - const app = getState().getIn(['auth', 'app']); +const createUserToken = (username: string, password: string) => + (dispatch: AppDispatch, getState: () => any) => { + const app = getState().auth.get('app'); const params = { client_id: app.get('client_id'), @@ -123,14 +122,13 @@ function createUserToken(username, password) { }; return dispatch(obtainOAuthToken(params)) - .then(token => dispatch(authLoggedIn(token))); + .then((token: Record) => dispatch(authLoggedIn(token))); }; -} -export function refreshUserToken() { - return (dispatch, getState) => { - const refreshToken = getState().getIn(['auth', 'user', 'refresh_token']); - const app = getState().getIn(['auth', 'app']); +export const refreshUserToken = () => + (dispatch: AppDispatch, getState: () => RootState) => { + const refreshToken = getState().auth.getIn(['user', 'refresh_token']); + const app = getState().auth.get('app'); if (!refreshToken) return dispatch(noOp); @@ -144,13 +142,12 @@ export function refreshUserToken() { }; return dispatch(obtainOAuthToken(params)) - .then(token => dispatch(authLoggedIn(token))); + .then((token: Record) => dispatch(authLoggedIn(token))); }; -} -export function otpVerify(code, mfa_token) { - return (dispatch, getState) => { - const app = getState().getIn(['auth', 'app']); +export const otpVerify = (code: string, mfa_token: string) => + (dispatch: AppDispatch, getState: () => any) => { + const app = getState().auth.get('app'); return api(getState, 'app').post('/oauth/mfa/challenge', { client_id: app.get('client_id'), client_secret: app.get('client_secret'), @@ -161,18 +158,17 @@ export function otpVerify(code, mfa_token) { scope: getScopes(getState()), }).then(({ data: token }) => dispatch(authLoggedIn(token))); }; -} -export function verifyCredentials(token, accountUrl) { +export const verifyCredentials = (token: string, accountUrl?: string) => { const baseURL = parseBaseURL(accountUrl); - return (dispatch, getState) => { + return (dispatch: AppDispatch, getState: () => any) => { dispatch({ type: VERIFY_CREDENTIALS_REQUEST, token }); return baseClient(token, baseURL).get('/api/v1/accounts/verify_credentials').then(({ data: account }) => { dispatch(importFetchedAccount(account)); dispatch({ type: VERIFY_CREDENTIALS_SUCCESS, token, account }); - if (account.id === getState().get('me')) dispatch(fetchMeSuccess(account)); + if (account.id === getState().me) dispatch(fetchMeSuccess(account)); return account; }).catch(error => { if (error?.response?.status === 403 && error?.response?.data?.id) { @@ -180,75 +176,64 @@ export function verifyCredentials(token, accountUrl) { const account = error.response.data; dispatch(importFetchedAccount(account)); dispatch({ type: VERIFY_CREDENTIALS_SUCCESS, token, account }); - if (account.id === getState().get('me')) dispatch(fetchMeSuccess(account)); + if (account.id === getState().me) dispatch(fetchMeSuccess(account)); return account; } else { - if (getState().get('me') === null) dispatch(fetchMeFail(error)); + if (getState().me === null) dispatch(fetchMeFail(error)); dispatch({ type: VERIFY_CREDENTIALS_FAIL, token, error, skipAlert: true }); return error; } }); }; -} +}; -export function rememberAuthAccount(accountUrl) { - return (dispatch, getState) => { +export const rememberAuthAccount = (accountUrl: string) => + (dispatch: AppDispatch, getState: () => any) => { dispatch({ type: AUTH_ACCOUNT_REMEMBER_REQUEST, accountUrl }); return KVStore.getItemOrError(`authAccount:${accountUrl}`).then(account => { dispatch(importFetchedAccount(account)); dispatch({ type: AUTH_ACCOUNT_REMEMBER_SUCCESS, account, accountUrl }); - if (account.id === getState().get('me')) dispatch(fetchMeSuccess(account)); + if (account.id === getState().me) dispatch(fetchMeSuccess(account)); return account; }).catch(error => { dispatch({ type: AUTH_ACCOUNT_REMEMBER_FAIL, error, accountUrl, skipAlert: true }); }); }; -} -export function loadCredentials(token, accountUrl) { - return (dispatch, getState) => { - return dispatch(rememberAuthAccount(accountUrl)) - .then(account => account) - .then(() => { - dispatch(verifyCredentials(token, accountUrl)); - }) - .catch(error => dispatch(verifyCredentials(token, accountUrl))); - }; -} +export const loadCredentials = (token: string, accountUrl: string) => + (dispatch: AppDispatch) => dispatch(rememberAuthAccount(accountUrl)) + .then(() => { + dispatch(verifyCredentials(token, accountUrl)); + }) + .catch(() => dispatch(verifyCredentials(token, accountUrl))); -export function logIn(intl, username, password) { - return (dispatch, getState) => { - return dispatch(getAuthApp()).then(() => { - return dispatch(createUserToken(username, password)); - }).catch(error => { - if (error.response.data.error === 'mfa_required') { - // If MFA is required, throw the error and handle it in the component. - throw error; - } else if (error.response.data.error === 'invalid_grant') { - // Mastodon returns this user-unfriendly error as a catch-all - // for everything from "bad request" to "wrong password". - // Assume our code is correct and it's a wrong password. - dispatch(snackbar.error(intl.formatMessage(messages.invalidCredentials))); - } else if (error.response.data.error) { - // If the backend returns an error, display it. - dispatch(snackbar.error(error.response.data.error)); - } else { - // Return "wrong password" message. - dispatch(snackbar.error(intl.formatMessage(messages.invalidCredentials))); - } +export const logIn = (username: string, password: string) => + (dispatch: AppDispatch) => dispatch(getAuthApp()).then(() => { + return dispatch(createUserToken(username, password)); + }).catch((error: AxiosError) => { + if ((error.response?.data as any).error === 'mfa_required') { + // If MFA is required, throw the error and handle it in the component. throw error; - }); - }; -} + } else if ((error.response?.data as any).error === 'invalid_grant') { + // Mastodon returns this user-unfriendly error as a catch-all + // for everything from "bad request" to "wrong password". + // Assume our code is correct and it's a wrong password. + dispatch(snackbar.error(messages.invalidCredentials)); + } else if ((error.response?.data as any).error) { + // If the backend returns an error, display it. + dispatch(snackbar.error((error.response?.data as any).error)); + } else { + // Return "wrong password" message. + dispatch(snackbar.error(messages.invalidCredentials)); + } + throw error; + }); -export function deleteSession() { - return (dispatch, getState) => { - return api(getState).delete('/api/sign_out'); - }; -} +export const deleteSession = () => + (dispatch: AppDispatch, getState: () => any) => api(getState).delete('/api/sign_out'); -export function logOut(intl) { - return (dispatch, getState) => { +export const logOut = () => + (dispatch: AppDispatch, getState: () => RootState) => { const state = getState(); const account = getLoggedInAccount(state); const standalone = isStandalone(state); @@ -256,9 +241,9 @@ export function logOut(intl) { if (!account) return dispatch(noOp); const params = { - client_id: state.getIn(['auth', 'app', 'client_id']), - client_secret: state.getIn(['auth', 'app', 'client_secret']), - token: state.getIn(['auth', 'users', account.url, 'access_token']), + client_id: state.auth.getIn(['app', 'client_id']), + client_secret: state.auth.getIn(['app', 'client_secret']), + token: state.auth.getIn(['users', account.url, 'access_token']), }; return Promise.all([ @@ -266,52 +251,47 @@ export function logOut(intl) { dispatch(deleteSession()), ]).finally(() => { dispatch({ type: AUTH_LOGGED_OUT, account, standalone }); - dispatch(snackbar.success(intl.formatMessage(messages.loggedOut))); + return dispatch(snackbar.success(messages.loggedOut)); }); }; -} -export function switchAccount(accountId, background = false) { - return (dispatch, getState) => { - const account = getState().getIn(['accounts', accountId]); - dispatch({ type: SWITCH_ACCOUNT, account, background }); +export const switchAccount = (accountId: string, background = false) => + (dispatch: AppDispatch, getState: () => any) => { + const account = getState().accounts.get(accountId); + return dispatch({ type: SWITCH_ACCOUNT, account, background }); }; -} -export function fetchOwnAccounts() { - return (dispatch, getState) => { +export const fetchOwnAccounts = () => + (dispatch: AppDispatch, getState: () => RootState) => { const state = getState(); - state.getIn(['auth', 'users']).forEach(user => { - const account = state.getIn(['accounts', user.get('id')]); + return state.auth.get('users').forEach((user: ImmutableMap) => { + const account = state.accounts.get(user.get('id')); if (!account) { - dispatch(verifyCredentials(user.get('access_token'), user.get('url'))); + dispatch(verifyCredentials(user.get('access_token')!, user.get('url'))); } }); }; -} -export function register(params) { - return (dispatch, getState) => { + +export const register = (params: Record) => + (dispatch: AppDispatch) => { params.fullname = params.username; return dispatch(createAppAndToken()) .then(() => dispatch(createAccount(params))) - .then(({ token }) => { + .then(({ token }: { token: Record }) => { dispatch(startOnboarding()); return dispatch(authLoggedIn(token)); }); }; -} -export function fetchCaptcha() { - return (dispatch, getState) => { +export const fetchCaptcha = () => + (_dispatch: AppDispatch, getState: () => any) => { return api(getState).get('/api/pleroma/captcha'); }; -} -export function authLoggedIn(token) { - return (dispatch, getState) => { +export const authLoggedIn = (token: Record) => + (dispatch: AppDispatch) => { dispatch({ type: AUTH_LOGGED_IN, token }); return token; }; -} diff --git a/app/soapbox/actions/backups.js b/app/soapbox/actions/backups.ts similarity index 63% rename from app/soapbox/actions/backups.js rename to app/soapbox/actions/backups.ts index 844c55ce5..2057782ba 100644 --- a/app/soapbox/actions/backups.js +++ b/app/soapbox/actions/backups.ts @@ -1,5 +1,7 @@ import api from '../api'; +import type { AppDispatch } from 'soapbox/store'; + export const BACKUPS_FETCH_REQUEST = 'BACKUPS_FETCH_REQUEST'; export const BACKUPS_FETCH_SUCCESS = 'BACKUPS_FETCH_SUCCESS'; export const BACKUPS_FETCH_FAIL = 'BACKUPS_FETCH_FAIL'; @@ -8,24 +10,22 @@ export const BACKUPS_CREATE_REQUEST = 'BACKUPS_CREATE_REQUEST'; export const BACKUPS_CREATE_SUCCESS = 'BACKUPS_CREATE_SUCCESS'; export const BACKUPS_CREATE_FAIL = 'BACKUPS_CREATE_FAIL'; -export function fetchBackups() { - return (dispatch, getState) => { +export const fetchBackups = () => + (dispatch: AppDispatch, getState: () => any) => { dispatch({ type: BACKUPS_FETCH_REQUEST }); - return api(getState).get('/api/v1/pleroma/backups').then(({ data: backups }) => { - dispatch({ type: BACKUPS_FETCH_SUCCESS, backups }); - }).catch(error => { + return api(getState).get('/api/v1/pleroma/backups').then(({ data: backups }) => + dispatch({ type: BACKUPS_FETCH_SUCCESS, backups }), + ).catch(error => { dispatch({ type: BACKUPS_FETCH_FAIL, error }); }); }; -} -export function createBackup() { - return (dispatch, getState) => { +export const createBackup = () => + (dispatch: AppDispatch, getState: () => any) => { dispatch({ type: BACKUPS_CREATE_REQUEST }); - return api(getState).post('/api/v1/pleroma/backups').then(({ data: backups }) => { - dispatch({ type: BACKUPS_CREATE_SUCCESS, backups }); - }).catch(error => { + return api(getState).post('/api/v1/pleroma/backups').then(({ data: backups }) => + dispatch({ type: BACKUPS_CREATE_SUCCESS, backups }), + ).catch(error => { dispatch({ type: BACKUPS_CREATE_FAIL, error }); }); }; -} diff --git a/app/soapbox/actions/external_auth.js b/app/soapbox/actions/external_auth.ts similarity index 63% rename from app/soapbox/actions/external_auth.js rename to app/soapbox/actions/external_auth.ts index 4f389667f..064e100c9 100644 --- a/app/soapbox/actions/external_auth.js +++ b/app/soapbox/actions/external_auth.ts @@ -18,7 +18,10 @@ import { getQuirks } from 'soapbox/utils/quirks'; import { baseClient } from '../api'; -const fetchExternalInstance = baseURL => { +import type { AppDispatch } from 'soapbox/store'; +import type { Instance } from 'soapbox/types/entities'; + +const fetchExternalInstance = (baseURL?: string) => { return baseClient(null, baseURL) .get('/api/v1/instance') .then(({ data: instance }) => normalizeInstance(instance)) @@ -33,8 +36,8 @@ const fetchExternalInstance = baseURL => { }); }; -function createExternalApp(instance, baseURL) { - return (dispatch, getState) => { +const createExternalApp = (instance: Instance, baseURL?: string) => + (dispatch: AppDispatch) => { // Mitra: skip creating the auth app if (getQuirks(instance).noApps) return new Promise(f => f({})); @@ -49,14 +52,13 @@ function createExternalApp(instance, baseURL) { return dispatch(createApp(params, baseURL)); }; -} -function externalAuthorize(instance, baseURL) { - return (dispatch, getState) => { +const externalAuthorize = (instance: Instance, baseURL: string) => + (dispatch: AppDispatch) => { const { scopes } = getFeatures(instance); - return dispatch(createExternalApp(instance, baseURL)).then(app => { - const { client_id, redirect_uri } = app; + return dispatch(createExternalApp(instance, baseURL)).then((app) => { + const { client_id, redirect_uri } = app as Record; const query = new URLSearchParams({ client_id, @@ -72,58 +74,56 @@ function externalAuthorize(instance, baseURL) { window.location.href = `${baseURL}/oauth/authorize?${query.toString()}`; }); }; -} -export function externalEthereumLogin(instance, baseURL) { - return (dispatch, getState) => { - const loginMessage = instance.get('login_message'); +const externalEthereumLogin = (instance: Instance, baseURL?: string) => + (dispatch: AppDispatch) => { + const loginMessage = instance.login_message; return getWalletAndSign(loginMessage).then(({ wallet, signature }) => { - return dispatch(createExternalApp(instance, baseURL)).then(app => { + return dispatch(createExternalApp(instance, baseURL)).then((app) => { + const { client_id, client_secret } = app as Record; const params = { grant_type: 'ethereum', wallet_address: wallet.toLowerCase(), - client_id: app.client_id, - client_secret: app.client_secret, - password: signature, + client_id: client_id, + client_secret: client_secret, + password: signature as string, redirect_uri: 'urn:ietf:wg:oauth:2.0:oob', scope: getFeatures(instance).scopes, }; return dispatch(obtainOAuthToken(params, baseURL)) - .then(token => dispatch(authLoggedIn(token))) - .then(({ access_token }) => dispatch(verifyCredentials(access_token, baseURL))) - .then(account => dispatch(switchAccount(account.id))) + .then((token: Record) => dispatch(authLoggedIn(token))) + .then(({ access_token }: any) => dispatch(verifyCredentials(access_token, baseURL))) + .then((account: { id: string }) => dispatch(switchAccount(account.id))) .then(() => window.location.href = '/'); }); }); }; -} -export function externalLogin(host) { - return (dispatch, getState) => { +export const externalLogin = (host: string) => + (dispatch: AppDispatch) => { const baseURL = parseBaseURL(host) || parseBaseURL(`https://${host}`); - return fetchExternalInstance(baseURL).then(instance => { + return fetchExternalInstance(baseURL).then((instance) => { const features = getFeatures(instance); const quirks = getQuirks(instance); if (features.ethereumLogin && quirks.noOAuthForm) { - return dispatch(externalEthereumLogin(instance, baseURL)); + dispatch(externalEthereumLogin(instance, baseURL)); } else { - return dispatch(externalAuthorize(instance, baseURL)); + dispatch(externalAuthorize(instance, baseURL)); } }); }; -} -export function loginWithCode(code) { - return (dispatch, getState) => { - const { client_id, client_secret, redirect_uri } = JSON.parse(localStorage.getItem('soapbox:external:app')); - const baseURL = localStorage.getItem('soapbox:external:baseurl'); - const scope = localStorage.getItem('soapbox:external:scopes'); +export const loginWithCode = (code: string) => + (dispatch: AppDispatch) => { + const { client_id, client_secret, redirect_uri } = JSON.parse(localStorage.getItem('soapbox:external:app')!); + const baseURL = localStorage.getItem('soapbox:external:baseurl')!; + const scope = localStorage.getItem('soapbox:external:scopes')!; - const params = { + const params: Record = { client_id, client_secret, redirect_uri, @@ -133,9 +133,8 @@ export function loginWithCode(code) { }; return dispatch(obtainOAuthToken(params, baseURL)) - .then(token => dispatch(authLoggedIn(token))) - .then(({ access_token }) => dispatch(verifyCredentials(access_token, baseURL))) - .then(account => dispatch(switchAccount(account.id))) + .then((token: Record) => dispatch(authLoggedIn(token))) + .then(({ access_token }: any) => dispatch(verifyCredentials(access_token as string, baseURL))) + .then((account: { id: string }) => dispatch(switchAccount(account.id))) .then(() => window.location.href = '/'); }; -} diff --git a/app/soapbox/actions/oauth.js b/app/soapbox/actions/oauth.ts similarity index 83% rename from app/soapbox/actions/oauth.js rename to app/soapbox/actions/oauth.ts index 9662972a8..aefef7b6f 100644 --- a/app/soapbox/actions/oauth.js +++ b/app/soapbox/actions/oauth.ts @@ -8,6 +8,8 @@ import { baseClient } from '../api'; +import type { AppDispatch } from 'soapbox/store'; + export const OAUTH_TOKEN_CREATE_REQUEST = 'OAUTH_TOKEN_CREATE_REQUEST'; export const OAUTH_TOKEN_CREATE_SUCCESS = 'OAUTH_TOKEN_CREATE_SUCCESS'; export const OAUTH_TOKEN_CREATE_FAIL = 'OAUTH_TOKEN_CREATE_FAIL'; @@ -16,8 +18,8 @@ export const OAUTH_TOKEN_REVOKE_REQUEST = 'OAUTH_TOKEN_REVOKE_REQUEST'; export const OAUTH_TOKEN_REVOKE_SUCCESS = 'OAUTH_TOKEN_REVOKE_SUCCESS'; export const OAUTH_TOKEN_REVOKE_FAIL = 'OAUTH_TOKEN_REVOKE_FAIL'; -export function obtainOAuthToken(params, baseURL) { - return (dispatch, getState) => { +export const obtainOAuthToken = (params: Record, baseURL?: string) => + (dispatch: AppDispatch) => { dispatch({ type: OAUTH_TOKEN_CREATE_REQUEST, params }); return baseClient(null, baseURL).post('/oauth/token', params).then(({ data: token }) => { dispatch({ type: OAUTH_TOKEN_CREATE_SUCCESS, params, token }); @@ -27,10 +29,9 @@ export function obtainOAuthToken(params, baseURL) { throw error; }); }; -} -export function revokeOAuthToken(params) { - return (dispatch, getState) => { +export const revokeOAuthToken = (params: Record) => + (dispatch: AppDispatch) => { dispatch({ type: OAUTH_TOKEN_REVOKE_REQUEST, params }); return baseClient().post('/oauth/revoke', params).then(({ data }) => { dispatch({ type: OAUTH_TOKEN_REVOKE_SUCCESS, params, data }); @@ -40,4 +41,3 @@ export function revokeOAuthToken(params) { throw error; }); }; -} diff --git a/app/soapbox/api.ts b/app/soapbox/api.ts index 05d08b768..33cd803ad 100644 --- a/app/soapbox/api.ts +++ b/app/soapbox/api.ts @@ -53,7 +53,7 @@ const getAuthBaseURL = createSelector([ * @param {string} baseURL * @returns {object} Axios instance */ -export const baseClient = (accessToken: string, baseURL: string = ''): AxiosInstance => { +export const baseClient = (accessToken?: string | null, baseURL: string = ''): AxiosInstance => { return axios.create({ // When BACKEND_URL is set, always use it. baseURL: isURL(BuildConfig.BACKEND_URL) ? BuildConfig.BACKEND_URL : baseURL, diff --git a/app/soapbox/components/birthday_input.tsx b/app/soapbox/components/birthday_input.tsx index 2b4e4833f..a1dc36c88 100644 --- a/app/soapbox/components/birthday_input.tsx +++ b/app/soapbox/components/birthday_input.tsx @@ -25,7 +25,7 @@ const BirthdayInput: React.FC = ({ value, onChange, required }) const features = useFeatures(); const supportsBirthdays = features.birthdays; - const minAge = useAppSelector((state) => state.instance.getIn(['pleroma', 'metadata', 'birthday_min_age'])) as number; + const minAge = useAppSelector((state) => state.instance.pleroma.getIn(['metadata', 'birthday_min_age'])) as number; const maxDate = useMemo(() => { if (!supportsBirthdays) return null; diff --git a/app/soapbox/components/sidebar-navigation.tsx b/app/soapbox/components/sidebar-navigation.tsx index e4d574a74..5b8c79230 100644 --- a/app/soapbox/components/sidebar-navigation.tsx +++ b/app/soapbox/components/sidebar-navigation.tsx @@ -18,7 +18,7 @@ const SidebarNavigation = () => { const settings = useAppSelector((state) => getSettings(state)); const account = useOwnAccount(); const notificationCount = useAppSelector((state) => state.notifications.get('unread')); - const chatsCount = useAppSelector((state) => state.chats.get('items').reduce((acc: any, curr: any) => acc + Math.min(curr.get('unread', 0), 1), 0)); + const chatsCount = useAppSelector((state) => state.chats.items.reduce((acc, curr) => acc + Math.min(curr.unread || 0, 1), 0)); const followRequestsCount = useAppSelector((state) => state.user_lists.getIn(['follow_requests', 'items'], ImmutableOrderedSet()).count()); const dashboardCount = useAppSelector((state) => state.admin.openReports.count() + state.admin.awaitingApproval.count()); diff --git a/app/soapbox/components/sidebar_menu.tsx b/app/soapbox/components/sidebar_menu.tsx index c53ec0b40..cb3962010 100644 --- a/app/soapbox/components/sidebar_menu.tsx +++ b/app/soapbox/components/sidebar_menu.tsx @@ -109,7 +109,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => { const onClickLogOut: React.MouseEventHandler = (e) => { e.preventDefault(); - dispatch(logOut(intl)); + dispatch(logOut()); }; const handleSwitcherClick: React.MouseEventHandler = (e) => { diff --git a/app/soapbox/components/thumb_navigation.tsx b/app/soapbox/components/thumb_navigation.tsx index 963b5d234..adb89869f 100644 --- a/app/soapbox/components/thumb_navigation.tsx +++ b/app/soapbox/components/thumb_navigation.tsx @@ -8,7 +8,7 @@ import { getFeatures } from 'soapbox/utils/features'; const ThumbNavigation: React.FC = (): JSX.Element => { const account = useOwnAccount(); const notificationCount = useAppSelector((state) => state.notifications.unread); - const chatsCount = useAppSelector((state) => state.chats.get('items').reduce((acc: number, curr: any) => acc + Math.min(curr.get('unread', 0), 1), 0)); + const chatsCount = useAppSelector((state) => state.chats.items.reduce((acc, curr) => acc + Math.min(curr.unread || 0, 1), 0)); const dashboardCount = useAppSelector((state) => state.admin.openReports.count() + state.admin.awaitingApproval.count()); const features = getFeatures(useAppSelector((state) => state.instance)); diff --git a/app/soapbox/features/auth_login/components/captcha.tsx b/app/soapbox/features/auth_login/components/captcha.tsx index 465ddc4fd..d967c76a2 100644 --- a/app/soapbox/features/auth_login/components/captcha.tsx +++ b/app/soapbox/features/auth_login/components/captcha.tsx @@ -6,6 +6,8 @@ import { fetchCaptcha } from 'soapbox/actions/auth'; import { Stack, Text, Input } from 'soapbox/components/ui'; import { useAppDispatch } from 'soapbox/hooks'; +import type { AxiosResponse } from 'axios'; + const noOp = () => {}; const messages = defineMessages({ @@ -39,7 +41,7 @@ const CaptchaField: React.FC = ({ const [refresh, setRefresh] = useState(undefined); const getCaptcha = () => { - dispatch(fetchCaptcha()).then(response => { + dispatch(fetchCaptcha()).then((response: AxiosResponse) => { const captcha = ImmutableMap(response.data); setCaptcha(captcha); onFetch(captcha); diff --git a/app/soapbox/features/auth_login/components/login_page.tsx b/app/soapbox/features/auth_login/components/login_page.tsx index 3f7834b7f..dd37e5f63 100644 --- a/app/soapbox/features/auth_login/components/login_page.tsx +++ b/app/soapbox/features/auth_login/components/login_page.tsx @@ -1,5 +1,4 @@ import React, { useState } from 'react'; -import { useIntl } from 'react-intl'; import { Redirect } from 'react-router-dom'; import { logIn, verifyCredentials, switchAccount } from 'soapbox/actions/auth'; @@ -15,7 +14,6 @@ import OtpAuthForm from './otp_auth_form'; import type { AxiosError } from 'axios'; const LoginPage = () => { - const intl = useIntl(); const dispatch = useAppDispatch(); const me = useAppSelector((state) => state.me); @@ -36,8 +34,8 @@ const LoginPage = () => { const handleSubmit: React.FormEventHandler = (event) => { const { username, password } = getFormData(event.target as HTMLFormElement); - dispatch(logIn(intl, username, password)).then(({ access_token }: { access_token: string }) => { - return dispatch(verifyCredentials(access_token)) + dispatch(logIn(username, password)).then(({ access_token }) => { + return dispatch(verifyCredentials(access_token as string)) // Refetch the instance for authenticated fetch .then(() => dispatch(fetchInstance() as any)); }).then((account: { id: string }) => { diff --git a/app/soapbox/features/auth_login/components/logout.tsx b/app/soapbox/features/auth_login/components/logout.tsx index 5c1bee915..6f70e0bdd 100644 --- a/app/soapbox/features/auth_login/components/logout.tsx +++ b/app/soapbox/features/auth_login/components/logout.tsx @@ -1,5 +1,4 @@ import React, { useEffect, useState } from 'react'; -import { useIntl } from 'react-intl'; import { useDispatch } from 'react-redux'; import { Redirect } from 'react-router-dom'; @@ -8,12 +7,11 @@ import { Spinner } from 'soapbox/components/ui'; /** Component that logs the user out when rendered */ const Logout: React.FC = () => { - const intl = useIntl(); const dispatch = useDispatch(); const [done, setDone] = useState(false); useEffect(() => { - dispatch(logOut(intl) as any) + dispatch(logOut() as any) .then(() => setDone(true)) .catch(console.warn); }, []); diff --git a/app/soapbox/features/auth_login/components/otp_auth_form.tsx b/app/soapbox/features/auth_login/components/otp_auth_form.tsx index eaeade08b..b4ef3d9d5 100644 --- a/app/soapbox/features/auth_login/components/otp_auth_form.tsx +++ b/app/soapbox/features/auth_login/components/otp_auth_form.tsx @@ -32,8 +32,8 @@ const OtpAuthForm: React.FC = ({ mfa_token }) => { const { code } = getFormData(event.target); dispatch(otpVerify(code, mfa_token)).then(({ access_token }) => { setCodeError(false); - return dispatch(verifyCredentials(access_token)); - }).then(account => { + return dispatch(verifyCredentials(access_token as string)); + }).then((account: Record) => { setShouldRedirect(true); return dispatch(switchAccount(account.id)); }).catch(() => { diff --git a/app/soapbox/features/backups/index.tsx b/app/soapbox/features/backups/index.tsx index d1baf4991..c78ceb298 100644 --- a/app/soapbox/features/backups/index.tsx +++ b/app/soapbox/features/backups/index.tsx @@ -2,10 +2,7 @@ import classNames from 'classnames'; import React, { useEffect, useState } from 'react'; import { defineMessages, useIntl } from 'react-intl'; -import { - fetchBackups, - createBackup, -} from 'soapbox/actions/backups'; +import { fetchBackups, createBackup } from 'soapbox/actions/backups'; import ScrollableList from 'soapbox/components/scrollable_list'; import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; diff --git a/app/soapbox/features/chats/components/chat.tsx b/app/soapbox/features/chats/components/chat.tsx index 8e10a2f91..63430dea4 100644 --- a/app/soapbox/features/chats/components/chat.tsx +++ b/app/soapbox/features/chats/components/chat.tsx @@ -20,7 +20,7 @@ interface IChat { const Chat: React.FC = ({ chatId, onClick }) => { const chat = useAppSelector((state) => { - const chat = state.chats.getIn(['items', chatId]); + const chat = state.chats.items.get(chatId); return chat ? getChat(state, (chat as any).toJS()) : undefined; }) as ChatEntity; diff --git a/app/soapbox/features/chats/components/chat_list.tsx b/app/soapbox/features/chats/components/chat_list.tsx index 8347c0e89..5d9c422a8 100644 --- a/app/soapbox/features/chats/components/chat_list.tsx +++ b/app/soapbox/features/chats/components/chat_list.tsx @@ -49,9 +49,9 @@ const ChatList: React.FC = ({ onClickChat, useWindowScroll = false }) const dispatch = useDispatch(); const intl = useIntl(); - const chatIds = useAppSelector(state => sortedChatIdsSelector(state.chats.get('items'))); - const hasMore = useAppSelector(state => !!state.chats.get('next')); - const isLoading = useAppSelector(state => state.chats.get('isLoading')); + const chatIds = useAppSelector(state => sortedChatIdsSelector(state.chats.items)); + const hasMore = useAppSelector(state => !!state.chats.next); + const isLoading = useAppSelector(state => state.chats.isLoading); const handleLoadMore = useCallback(() => { if (hasMore && !isLoading) { diff --git a/app/soapbox/features/public_layout/components/header.tsx b/app/soapbox/features/public_layout/components/header.tsx index 40eed3039..d44dfe450 100644 --- a/app/soapbox/features/public_layout/components/header.tsx +++ b/app/soapbox/features/public_layout/components/header.tsx @@ -48,7 +48,7 @@ const Header = () => { event.preventDefault(); setLoading(true); - dispatch(logIn(intl, username, password) as any) + dispatch(logIn(username, password) as any) .then(({ access_token }: { access_token: string }) => { return ( dispatch(verifyCredentials(access_token) as any) diff --git a/app/soapbox/features/ui/components/link_footer.tsx b/app/soapbox/features/ui/components/link_footer.tsx index b87ddaad6..4365ba9d5 100644 --- a/app/soapbox/features/ui/components/link_footer.tsx +++ b/app/soapbox/features/ui/components/link_footer.tsx @@ -1,6 +1,6 @@ import classNames from 'classnames'; import React from 'react'; -import { FormattedMessage, useIntl } from 'react-intl'; +import { FormattedMessage } from 'react-intl'; import { useDispatch } from 'react-redux'; import { Link } from 'react-router-dom'; @@ -27,11 +27,10 @@ const LinkFooter: React.FC = (): JSX.Element => { const features = useFeatures(); const soapboxConfig = useSoapboxConfig(); - const intl = useIntl(); const dispatch = useDispatch(); const onClickLogOut: React.EventHandler = (e) => { - dispatch(logOut(intl)); + dispatch(logOut()); e.preventDefault(); }; diff --git a/app/soapbox/features/ui/components/navbar.tsx b/app/soapbox/features/ui/components/navbar.tsx index eb403c8fc..ca0d02705 100644 --- a/app/soapbox/features/ui/components/navbar.tsx +++ b/app/soapbox/features/ui/components/navbar.tsx @@ -44,7 +44,7 @@ const Navbar = () => { event.preventDefault(); setLoading(true); - dispatch(logIn(intl, username, password) as any) + dispatch(logIn(username, password) as any) .then(({ access_token }: { access_token: string }) => { setLoading(false); diff --git a/app/soapbox/features/ui/components/profile-dropdown.tsx b/app/soapbox/features/ui/components/profile-dropdown.tsx index dd2d8a6fc..01cef2b66 100644 --- a/app/soapbox/features/ui/components/profile-dropdown.tsx +++ b/app/soapbox/features/ui/components/profile-dropdown.tsx @@ -43,7 +43,7 @@ const ProfileDropdown: React.FC = ({ account, children }) => { const otherAccounts = useAppSelector((state) => authUsers.map((authUser: any) => getAccount(state, authUser.get('id')))); const handleLogOut = () => { - dispatch(logOut(intl)); + dispatch(logOut()); }; const handleSwitchAccount = (account: AccountEntity) => { diff --git a/app/soapbox/features/verification/registration.tsx b/app/soapbox/features/verification/registration.tsx index 3c527b828..1efaecca6 100644 --- a/app/soapbox/features/verification/registration.tsx +++ b/app/soapbox/features/verification/registration.tsx @@ -50,7 +50,7 @@ const Registration = () => { // TODO: handle validation errors from Pepe dispatch(createAccount(username, password)) - .then(() => dispatch(logIn(intl, username, password))) + .then(() => dispatch(logIn(username, password))) .then(({ access_token }: any) => dispatch(verifyCredentials(access_token))) .then(() => dispatch(fetchInstance())) .then(() => { diff --git a/app/soapbox/features/verification/waitlist_page.tsx b/app/soapbox/features/verification/waitlist_page.tsx index 00182e58f..71677d3d3 100644 --- a/app/soapbox/features/verification/waitlist_page.tsx +++ b/app/soapbox/features/verification/waitlist_page.tsx @@ -1,5 +1,4 @@ import React, { useEffect } from 'react'; -import { useIntl } from 'react-intl'; import { useDispatch } from 'react-redux'; import { Link } from 'react-router-dom'; @@ -12,7 +11,6 @@ import { useAppSelector, useOwnAccount } from 'soapbox/hooks'; const WaitlistPage = (/* { account } */) => { const dispatch = useDispatch(); - const intl = useIntl(); const title = useAppSelector((state) => state.instance.title); const me = useOwnAccount(); @@ -20,7 +18,7 @@ const WaitlistPage = (/* { account } */) => { const onClickLogOut: React.MouseEventHandler = (event) => { event.preventDefault(); - dispatch(logOut(intl)); + dispatch(logOut()); }; const openVerifySmsModal = () => dispatch(openModal('VERIFY_SMS')); diff --git a/app/soapbox/hooks/useAppDispatch.ts b/app/soapbox/hooks/useAppDispatch.ts index 11e7226d5..fa28aa0d2 100644 --- a/app/soapbox/hooks/useAppDispatch.ts +++ b/app/soapbox/hooks/useAppDispatch.ts @@ -1,5 +1,5 @@ import { useDispatch } from 'react-redux'; -import { AppDispatch } from 'soapbox/store'; +import type { AppDispatch } from 'soapbox/store'; export const useAppDispatch = () => useDispatch(); \ No newline at end of file diff --git a/app/soapbox/normalizers/instance.ts b/app/soapbox/normalizers/instance.ts index 7d908b3dc..e137c2165 100644 --- a/app/soapbox/normalizers/instance.ts +++ b/app/soapbox/normalizers/instance.ts @@ -39,6 +39,7 @@ export const InstanceRecord = ImmutableRecord({ fedibird_capabilities: ImmutableList(), invites_enabled: false, languages: ImmutableList(), + login_message: '', pleroma: ImmutableMap({ metadata: ImmutableMap({ account_activation_required: false, diff --git a/app/soapbox/selectors/index.ts b/app/soapbox/selectors/index.ts index 41b9edf11..316517a55 100644 --- a/app/soapbox/selectors/index.ts +++ b/app/soapbox/selectors/index.ts @@ -244,8 +244,8 @@ type APIChat = { id: string, last_message: string }; export const makeGetChat = () => { return createSelector( [ - (state: RootState, { id }: APIChat) => state.chats.getIn(['items', id]) as ReducerChat, - (state: RootState, { id }: APIChat) => state.accounts.get(state.chats.getIn(['items', id, 'account'])), + (state: RootState, { id }: APIChat) => state.chats.items.get(id) as ReducerChat, + (state: RootState, { id }: APIChat) => state.accounts.get(state.chats.items.getIn([id, 'account'])), (state: RootState, { last_message }: APIChat) => state.chat_messages.get(last_message), ], diff --git a/app/soapbox/utils/auth.ts b/app/soapbox/utils/auth.ts index 1a5b1b38e..dcb84104e 100644 --- a/app/soapbox/utils/auth.ts +++ b/app/soapbox/utils/auth.ts @@ -30,11 +30,11 @@ export const isLoggedIn = (getState: () => RootState) => { return validId(getState().me); }; -export const getAppToken = (state: RootState) => state.auth.getIn(['app', 'access_token']); +export const getAppToken = (state: RootState) => state.auth.getIn(['app', 'access_token']) as string; export const getUserToken = (state: RootState, accountId?: string | false | null) => { const accountUrl = state.accounts.getIn([accountId, 'url']); - return state.auth.getIn(['users', accountUrl, 'access_token']); + return state.auth.getIn(['users', accountUrl, 'access_token']) as string; }; export const getAccessToken = (state: RootState) => {