From 9188fcad956b92eea6c9db216e04887a9b27a7f6 Mon Sep 17 00:00:00 2001 From: mkljczk Date: Sat, 15 Mar 2025 12:30:15 +0100 Subject: [PATCH] pl-fe: migrate tokens list Signed-off-by: mkljczk --- packages/pl-fe/src/actions/security.ts | 26 +------------------ .../src/features/auth-token-list/index.tsx | 22 +++++++--------- .../src/queries/security/oauth-tokens.ts | 24 +++++++++++++++++ packages/pl-fe/src/reducers/security.ts | 17 +----------- 4 files changed, 36 insertions(+), 53 deletions(-) create mode 100644 packages/pl-fe/src/queries/security/oauth-tokens.ts diff --git a/packages/pl-fe/src/actions/security.ts b/packages/pl-fe/src/actions/security.ts index 4b48cdb58..43ba43d22 100644 --- a/packages/pl-fe/src/actions/security.ts +++ b/packages/pl-fe/src/actions/security.ts @@ -11,26 +11,9 @@ import { normalizeUsername } from 'pl-fe/utils/input'; import { AUTH_LOGGED_OUT, messages } from './auth'; -import type { OauthToken } from 'pl-api'; import type { Account } from 'pl-fe/normalizers/account'; import type { AppDispatch, RootState } from 'pl-fe/store'; -const FETCH_TOKENS_SUCCESS = 'FETCH_TOKENS_SUCCESS' as const; - -const REVOKE_TOKEN_SUCCESS = 'REVOKE_TOKEN_SUCCESS' as const; - -const fetchOAuthTokens = () => - (dispatch: AppDispatch, getState: () => RootState) => - getClient(getState).settings.getOauthTokens().then(({ items: tokens }) => { - dispatch({ type: FETCH_TOKENS_SUCCESS, tokens }); - }); - -const revokeOAuthTokenById = (tokenId: string) => - (dispatch: AppDispatch, getState: () => RootState) => - getClient(getState).settings.deleteOauthToken(tokenId).then(() => { - dispatch({ type: REVOKE_TOKEN_SUCCESS, tokenId }); - }); - const changePassword = (oldPassword: string, newPassword: string) => (dispatch: AppDispatch, getState: () => RootState) => getClient(getState).settings.changePassword(oldPassword, newPassword); @@ -63,16 +46,9 @@ const moveAccount = (targetAccount: string, password: string) => (dispatch: AppDispatch, getState: () => RootState) => getClient(getState).settings.moveAccount(targetAccount, password); -type SecurityAction = - | { type: typeof FETCH_TOKENS_SUCCESS; tokens: Array } - | { type: typeof REVOKE_TOKEN_SUCCESS; tokenId: string } - | { type: typeof AUTH_LOGGED_OUT; account: Account } +type SecurityAction = { type: typeof AUTH_LOGGED_OUT; account: Account } export { - FETCH_TOKENS_SUCCESS, - REVOKE_TOKEN_SUCCESS, - fetchOAuthTokens, - revokeOAuthTokenById, changePassword, resetPassword, changeEmail, diff --git a/packages/pl-fe/src/features/auth-token-list/index.tsx b/packages/pl-fe/src/features/auth-token-list/index.tsx index 59eac94cf..6aa241d63 100644 --- a/packages/pl-fe/src/features/auth-token-list/index.tsx +++ b/packages/pl-fe/src/features/auth-token-list/index.tsx @@ -1,7 +1,7 @@ -import React, { useEffect } from 'react'; +import { useInfiniteQuery, useMutation } from '@tanstack/react-query'; +import React from 'react'; import { defineMessages, FormattedDate, FormattedMessage, useIntl } from 'react-intl'; -import { fetchOAuthTokens, revokeOAuthTokenById } from 'pl-fe/actions/security'; import Badge from 'pl-fe/components/badge'; import Button from 'pl-fe/components/ui/button'; import Card, { CardBody, CardHeader, CardTitle } from 'pl-fe/components/ui/card'; @@ -11,8 +11,8 @@ import Icon from 'pl-fe/components/ui/icon'; import Spinner from 'pl-fe/components/ui/spinner'; import Stack from 'pl-fe/components/ui/stack'; import Text from 'pl-fe/components/ui/text'; -import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch'; import { useAppSelector } from 'pl-fe/hooks/use-app-selector'; +import { oauthTokensQueryOptions, revokeOauthTokenMutationOptions } from 'pl-fe/queries/security/oauth-tokens'; import { useModalsStore } from 'pl-fe/stores/modals'; import type { OauthToken } from 'pl-api'; @@ -31,9 +31,10 @@ interface IAuthToken { } const AuthToken: React.FC = ({ token, isCurrent }) => { - const dispatch = useAppDispatch(); const intl = useIntl(); + const revokeMutation = useMutation(revokeOauthTokenMutationOptions(token.id)); + const { openModal } = useModalsStore(); const handleRevoke = () => { @@ -43,11 +44,11 @@ const AuthToken: React.FC = ({ token, isCurrent }) => { message: intl.formatMessage(messages.revokeSessionMessage), confirm: intl.formatMessage(messages.revokeSessionConfirm), onConfirm: () => { - dispatch(revokeOAuthTokenById(token.id)); + revokeMutation.mutate(); }, }); else { - dispatch(revokeOAuthTokenById(token.id)); + revokeMutation.mutate(); } }; @@ -144,19 +145,16 @@ const AuthToken: React.FC = ({ token, isCurrent }) => { }; const AuthTokenList: React.FC = () => { - const dispatch = useAppDispatch(); const intl = useIntl(); - const tokens = useAppSelector(state => state.security.tokens.toReversed()); + + const { data: tokens } = useInfiniteQuery(oauthTokensQueryOptions); + const currentTokenId = useAppSelector(state => { const currentToken = Object.values(state.auth.tokens).find((token) => token.me === state.auth.me); return currentToken?.id; }); - useEffect(() => { - dispatch(fetchOAuthTokens()); - }, []); - const body = tokens ? (
{tokens.map((token) => ( diff --git a/packages/pl-fe/src/queries/security/oauth-tokens.ts b/packages/pl-fe/src/queries/security/oauth-tokens.ts new file mode 100644 index 000000000..003fa5de4 --- /dev/null +++ b/packages/pl-fe/src/queries/security/oauth-tokens.ts @@ -0,0 +1,24 @@ +import { create } from 'mutative'; + +import { getClient } from 'pl-fe/api'; + +import { queryClient } from '../client'; +import { makePaginatedResponseQueryOptions } from '../utils/make-paginated-response-query-options'; +import { mutationOptions } from '../utils/mutation-options'; + +const oauthTokensQueryOptions = makePaginatedResponseQueryOptions( + ['security', 'oauthTokens'], + (client) => client.settings.getOauthTokens(), +)(); + +const revokeOauthTokenMutationOptions = (oauthTokenId: string) => mutationOptions({ + mutationKey: ['security', 'oauthTokens', oauthTokenId], + mutationFn: () => getClient().settings.deleteOauthToken(oauthTokenId), + onSettled: () => { + queryClient.setQueryData(oauthTokensQueryOptions.queryKey, (data) => create(data, (draft) => { + draft?.pages.forEach(page => page.items = page.items.filter(({ id }) => id !== oauthTokenId)); + })); + }, +}); + +export { oauthTokensQueryOptions, revokeOauthTokenMutationOptions }; diff --git a/packages/pl-fe/src/reducers/security.ts b/packages/pl-fe/src/reducers/security.ts index d858b75fe..9f401a287 100644 --- a/packages/pl-fe/src/reducers/security.ts +++ b/packages/pl-fe/src/reducers/security.ts @@ -6,19 +6,14 @@ import { MFA_DISABLE_SUCCESS, type MfaAction, } from '../actions/mfa'; -import { FETCH_TOKENS_SUCCESS, REVOKE_TOKEN_SUCCESS, type SecurityAction } from '../actions/security'; - -import type { OauthToken } from 'pl-api'; interface State { - tokens: Array; mfa: { settings: Record; }; } const initialState: State = { - tokens: [], mfa: { settings: { totp: false, @@ -26,24 +21,14 @@ const initialState: State = { }, }; -const deleteToken = (state: State, tokenId: string) => state.tokens = state.tokens.filter(token => token.id !== tokenId); - const importMfa = (state: State, data: any) => state.mfa = data; const enableMfa = (state: State, method: string) => state.mfa.settings = { ...state.mfa.settings, [method]: true }; const disableMfa = (state: State, method: string) => state.mfa.settings = { ...state.mfa.settings, [method]: false }; -const security = (state = initialState, action: MfaAction | SecurityAction) => { +const security = (state = initialState, action: MfaAction) => { switch (action.type) { - case FETCH_TOKENS_SUCCESS: - return create(state, (draft) => { - draft.tokens = action.tokens; - }); - case REVOKE_TOKEN_SUCCESS: - return create(state, (draft) => { - deleteToken(draft, action.tokenId); - }); case MFA_FETCH_SUCCESS: return create(state, (draft) => { importMfa(draft, action.data);