diff --git a/app/soapbox/actions/compose.ts b/app/soapbox/actions/compose.ts index 61303d15b..c5cd29428 100644 --- a/app/soapbox/actions/compose.ts +++ b/app/soapbox/actions/compose.ts @@ -22,9 +22,9 @@ import { createStatus } from './statuses'; import type { AutoSuggestion } from 'soapbox/components/autosuggest-input'; import type { Emoji } from 'soapbox/features/emoji'; -import type { Group } from 'soapbox/schemas'; +import type { Account, Group } from 'soapbox/schemas'; import type { AppDispatch, RootState } from 'soapbox/store'; -import type { Account, APIEntity, Status, Tag } from 'soapbox/types/entities'; +import type { APIEntity, Status, Tag } from 'soapbox/types/entities'; import type { History } from 'soapbox/types/history'; const { CancelToken, isCancel } = axios; diff --git a/app/soapbox/actions/mutes.ts b/app/soapbox/actions/mutes.ts index bb684b0d6..a2379d44a 100644 --- a/app/soapbox/actions/mutes.ts +++ b/app/soapbox/actions/mutes.ts @@ -8,8 +8,9 @@ import { importFetchedAccounts } from './importer'; import { openModal } from './modals'; import type { AxiosError } from 'axios'; +import type { Account as AccountEntity } from 'soapbox/schemas'; import type { AppDispatch, RootState } from 'soapbox/store'; -import type { APIEntity, Account as AccountEntity } from 'soapbox/types/entities'; +import type { APIEntity } from 'soapbox/types/entities'; const MUTES_FETCH_REQUEST = 'MUTES_FETCH_REQUEST'; const MUTES_FETCH_SUCCESS = 'MUTES_FETCH_SUCCESS'; diff --git a/app/soapbox/api/hooks/accounts/useAccount.ts b/app/soapbox/api/hooks/accounts/useAccount.ts index 222a7c94f..1bfb06ab7 100644 --- a/app/soapbox/api/hooks/accounts/useAccount.ts +++ b/app/soapbox/api/hooks/accounts/useAccount.ts @@ -9,10 +9,11 @@ function useAccount(accountId?: string) { const api = useApi(); const { entity: account, ...result } = useEntity( - [Entities.ACCOUNTS, accountId || ''], + [Entities.ACCOUNTS, accountId!], () => api.get(`/api/v1/accounts/${accountId}`), { schema: accountSchema, enabled: !!accountId }, ); + const { relationships, isLoading: isRelationshipLoading, diff --git a/app/soapbox/api/hooks/accounts/useAccountLookup.ts b/app/soapbox/api/hooks/accounts/useAccountLookup.ts new file mode 100644 index 000000000..22753e180 --- /dev/null +++ b/app/soapbox/api/hooks/accounts/useAccountLookup.ts @@ -0,0 +1,31 @@ +import { Entities } from 'soapbox/entity-store/entities'; +import { useEntityLookup } from 'soapbox/entity-store/hooks'; +import { useApi } from 'soapbox/hooks/useApi'; +import { type Account, accountSchema } from 'soapbox/schemas'; + +import { useRelationships } from './useRelationships'; + +function useAccountLookup(acct?: string) { + const api = useApi(); + + const { entity: account, ...result } = useEntityLookup( + Entities.ACCOUNTS, + (account) => account.acct === acct, + () => api.get(`/api/v1/accounts/lookup?acct=${acct}`), + { schema: accountSchema, enabled: !!acct }, + ); + + const { + relationships, + isLoading: isRelationshipLoading, + } = useRelationships(account ? [account.id] : []); + + return { + ...result, + isLoading: result.isLoading, + isRelationshipLoading, + account: account ? { ...account, relationship: relationships[0] || null } : undefined, + }; +} + +export { useAccountLookup }; \ No newline at end of file diff --git a/app/soapbox/api/hooks/accounts/useRelationships.ts b/app/soapbox/api/hooks/accounts/useRelationships.ts index 48dfb7ecf..835a67664 100644 --- a/app/soapbox/api/hooks/accounts/useRelationships.ts +++ b/app/soapbox/api/hooks/accounts/useRelationships.ts @@ -7,10 +7,11 @@ import { type Relationship, relationshipSchema } from 'soapbox/schemas'; function useRelationships(ids: string[]) { const api = useApi(); const { isLoggedIn } = useLoggedIn(); + const q = ids.map(id => `id[]=${id}`).join('&'); const { entities: relationships, ...result } = useEntities( - [Entities.RELATIONSHIPS], - () => api.get(`/api/v1/accounts/relationships?${ids.map(id => `id[]=${id}`).join('&')}`), + [Entities.RELATIONSHIPS, q], + () => api.get(`/api/v1/accounts/relationships?${q}`), { schema: relationshipSchema, enabled: isLoggedIn && ids.filter(Boolean).length > 0 }, ); diff --git a/app/soapbox/api/hooks/index.ts b/app/soapbox/api/hooks/index.ts index 3e9d3151d..157304dd7 100644 --- a/app/soapbox/api/hooks/index.ts +++ b/app/soapbox/api/hooks/index.ts @@ -1,6 +1,7 @@ // Accounts export { useAccount } from './accounts/useAccount'; +export { useAccountLookup } from './accounts/useAccountLookup'; export { useFollow } from './accounts/useFollow'; export { useRelationships } from './accounts/useRelationships'; export { usePatronUser } from './accounts/usePatronUser'; diff --git a/app/soapbox/components/account.tsx b/app/soapbox/components/account.tsx index 5ce11c0a2..56491a7c6 100644 --- a/app/soapbox/components/account.tsx +++ b/app/soapbox/components/account.tsx @@ -15,10 +15,9 @@ import { Avatar, Emoji, HStack, Icon, IconButton, Stack, Text } from './ui'; import type { StatusApprovalStatus } from 'soapbox/normalizers/status'; import type { Account as AccountSchema } from 'soapbox/schemas'; -import type { Account as AccountEntity } from 'soapbox/types/entities'; interface IInstanceFavicon { - account: AccountEntity | AccountSchema + account: AccountSchema disabled?: boolean } @@ -68,7 +67,7 @@ const ProfilePopper: React.FC = ({ condition, wrapper, children }; export interface IAccount { - account: AccountEntity | AccountSchema + account: AccountSchema action?: React.ReactElement actionAlignment?: 'center' | 'top' actionIcon?: string diff --git a/app/soapbox/features/account-timeline/components/moved-note.tsx b/app/soapbox/features/account-timeline/components/moved-note.tsx index 38c1a8e2c..faa44c6ee 100644 --- a/app/soapbox/features/account-timeline/components/moved-note.tsx +++ b/app/soapbox/features/account-timeline/components/moved-note.tsx @@ -5,7 +5,7 @@ import Account from 'soapbox/components/account'; import Icon from 'soapbox/components/icon'; import { HStack, Text } from 'soapbox/components/ui'; -import type { Account as AccountEntity } from 'soapbox/types/entities'; +import type { Account as AccountEntity } from 'soapbox/schemas'; interface IMovedNote { from: AccountEntity diff --git a/app/soapbox/features/account/components/header.tsx b/app/soapbox/features/account/components/header.tsx index 3398967fa..3f7c175a7 100644 --- a/app/soapbox/features/account/components/header.tsx +++ b/app/soapbox/features/account/components/header.tsx @@ -28,8 +28,8 @@ import { useAppDispatch, useAppSelector, useFeatures, useOwnAccount } from 'soap import { normalizeAttachment } from 'soapbox/normalizers'; import { ChatKeys, useChats } from 'soapbox/queries/chats'; import { queryClient } from 'soapbox/queries/client'; +import { Account } from 'soapbox/schemas'; import toast from 'soapbox/toast'; -import { Account } from 'soapbox/types/entities'; import { isDefaultHeader, isLocal, isRemote } from 'soapbox/utils/accounts'; import copy from 'soapbox/utils/copy'; import { MASTODON, parseVersion } from 'soapbox/utils/features'; @@ -576,7 +576,7 @@ const Header: React.FC = ({ account }) => { disabled={createAndNavigateToChat.isLoading} /> ); - } else if (account.getIn(['pleroma', 'accepts_chat_messages']) === true) { + } else if (account.pleroma?.accepts_chat_messages) { return ( = (props) => { const [loading, setLoading] = useState(true); const username = props.params?.username || ''; - const account = useAppSelector(state => findAccountByUsername(state, username)); + const { account } = useAccountLookup(username); const isOwnAccount = username.toLowerCase() === ownAccount?.username?.toLowerCase(); const accountIds = useAppSelector(state => state.user_lists.followers.get(account!?.id)?.items || ImmutableOrderedSet()); diff --git a/app/soapbox/features/following/index.tsx b/app/soapbox/features/following/index.tsx index bece30fe0..7f2b51236 100644 --- a/app/soapbox/features/following/index.tsx +++ b/app/soapbox/features/following/index.tsx @@ -9,12 +9,12 @@ import { expandFollowing, fetchAccountByUsername, } from 'soapbox/actions/accounts'; +import { useAccountLookup } from 'soapbox/api/hooks'; import MissingIndicator from 'soapbox/components/missing-indicator'; import ScrollableList from 'soapbox/components/scrollable-list'; import { Column, Spinner } from 'soapbox/components/ui'; import AccountContainer from 'soapbox/containers/account-container'; import { useAppDispatch, useAppSelector, useFeatures, useOwnAccount } from 'soapbox/hooks'; -import { findAccountByUsername } from 'soapbox/selectors'; const messages = defineMessages({ heading: { id: 'column.following', defaultMessage: 'Following' }, @@ -36,7 +36,7 @@ const Following: React.FC = (props) => { const [loading, setLoading] = useState(true); const username = props.params?.username || ''; - const account = useAppSelector(state => findAccountByUsername(state, username)); + const { account } = useAccountLookup(username); const isOwnAccount = username.toLowerCase() === ownAccount?.username?.toLowerCase(); const accountIds = useAppSelector(state => state.user_lists.following.get(account!?.id)?.items || ImmutableOrderedSet()); diff --git a/app/soapbox/features/ui/components/action-button.tsx b/app/soapbox/features/ui/components/action-button.tsx index a0ade2a69..54c38ff7c 100644 --- a/app/soapbox/features/ui/components/action-button.tsx +++ b/app/soapbox/features/ui/components/action-button.tsx @@ -15,7 +15,6 @@ import { Button, HStack } from 'soapbox/components/ui'; import { useAppDispatch, useFeatures, useLoggedIn } from 'soapbox/hooks'; import type { Account } from 'soapbox/schemas'; -import type { Account as AccountEntity } from 'soapbox/types/entities'; const messages = defineMessages({ block: { id: 'account.block', defaultMessage: 'Block @{name}' }, @@ -35,7 +34,7 @@ const messages = defineMessages({ interface IActionButton { /** Target account for the action. */ - account: AccountEntity | Account + account: Account /** Type of action to prioritize, eg on Blocks and Mutes pages. */ actionType?: 'muting' | 'blocking' | 'follow_request' /** Displays shorter text on the "Awaiting approval" button. */ diff --git a/app/soapbox/features/ui/components/modals/verify-sms-modal.tsx b/app/soapbox/features/ui/components/modals/verify-sms-modal.tsx index 1d245db0a..b092763a4 100644 --- a/app/soapbox/features/ui/components/modals/verify-sms-modal.tsx +++ b/app/soapbox/features/ui/components/modals/verify-sms-modal.tsx @@ -177,6 +177,7 @@ const VerifySmsModal: React.FC = ({ onClose }) => { }; const submitVerification = () => { + if (!accessToken) return; // TODO: handle proper validation from Pepe -- expired vs invalid dispatch(reConfirmPhoneVerification(verificationCode)) .then(() => { diff --git a/app/soapbox/pages/profile-page.tsx b/app/soapbox/pages/profile-page.tsx index 9f5d27487..6d0f74d4f 100644 --- a/app/soapbox/pages/profile-page.tsx +++ b/app/soapbox/pages/profile-page.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; import { Redirect, useHistory } from 'react-router-dom'; +import { useAccountLookup } from 'soapbox/api/hooks'; import { Column, Layout, Tabs } from 'soapbox/components/ui'; import Header from 'soapbox/features/account/components/header'; import LinkFooter from 'soapbox/features/ui/components/link-footer'; @@ -16,7 +17,6 @@ import { PinnedAccountsPanel, } from 'soapbox/features/ui/util/async-components'; import { useAppSelector, useFeatures, useSoapboxConfig } from 'soapbox/hooks'; -import { findAccountByUsername, makeGetAccount } from 'soapbox/selectors'; import { getAcct, isLocal } from 'soapbox/utils/accounts'; interface IProfilePage { @@ -26,21 +26,14 @@ interface IProfilePage { children: React.ReactNode } -const getAccount = makeGetAccount(); - /** Page to display a user's profile. */ const ProfilePage: React.FC = ({ params, children }) => { const history = useHistory(); const username = params?.username || ''; - const account = useAppSelector(state => { - if (username) { - const account = findAccountByUsername(state, username); - if (account) { - return getAccount(state, account.id) || undefined; - } - } - }); + const { account } = useAccountLookup(username); + + console.log(account?.relationship); const me = useAppSelector(state => state.me); const features = useFeatures(); diff --git a/app/soapbox/stream.ts b/app/soapbox/stream.ts index 9370a20ee..a15ce02e8 100644 --- a/app/soapbox/stream.ts +++ b/app/soapbox/stream.ts @@ -48,7 +48,7 @@ export function connectStream( // If the WebSocket fails to be created, don't crash the whole page, // just proceed without a subscription. try { - subscription = getStream(streamingAPIBaseURL!, accessToken, path, { + subscription = getStream(streamingAPIBaseURL!, accessToken!, path, { connected() { if (pollingRefresh) { clearPolling(); diff --git a/app/soapbox/utils/auth.ts b/app/soapbox/utils/auth.ts index 9c5d14d6f..066025422 100644 --- a/app/soapbox/utils/auth.ts +++ b/app/soapbox/utils/auth.ts @@ -34,8 +34,10 @@ export const isLoggedIn = (getState: () => RootState) => { export const getAppToken = (state: RootState) => state.auth.app.access_token as string; export const getUserToken = (state: RootState, accountId?: string | false | null) => { - const accountUrl = state.accounts.getIn([accountId, 'url']) as string; - return state.auth.users.get(accountUrl)?.access_token as string; + if (!accountId) return; + const accountUrl = state.accounts[accountId]?.url; + if (!accountUrl) return; + return state.auth.users.get(accountUrl)?.access_token; }; export const getAccessToken = (state: RootState) => {