diff --git a/shared/chat/blocking/block-modal.tsx b/shared/chat/blocking/block-modal.tsx index cb87d39e0c84..b4c88115080c 100644 --- a/shared/chat/blocking/block-modal.tsx +++ b/shared/chat/blocking/block-modal.tsx @@ -1,6 +1,8 @@ import * as C from '@/constants' import * as React from 'react' import * as Kb from '@/common-adapters' +import * as T from '@/constants/types' +import * as S from '@/constants/strings' import * as Chat from '@/stores/chat' import {useTeamsState} from '@/stores/teams' import {useUsersState} from '@/stores/users' @@ -140,23 +142,30 @@ const Container = function BlockModal(ownProps: OwnProps) { const navigateUp = C.useRouterState(s => s.dispatch.navigateUp) const leaveTeam = useTeamsState(s => s.dispatch.leaveTeam) + const reportUserRPC = C.useRPC(T.RPCGen.userReportUserRpcPromise) + const setUserBlocksRPC = C.useRPC(T.RPCGen.userSetUserBlocksRpcPromise) const leaveTeamAndBlock = (teamname: string) => { leaveTeam(teamname, true, 'chat') } const getBlockState = useUsersState(s => s.dispatch.getBlockState) - const _reportUser = useUsersState(s => s.dispatch.reportUser) const refreshBlocksFor = getBlockState const reportUser = (username: string, conversationIDKey: string | undefined, report: ReportSettings) => { - _reportUser({ - comment: report.extraNotes, - conversationIDKey, - includeTranscript: report.includeTranscript && !!conversationIDKey, - reason: report.reason, - username, - }) + reportUserRPC( + [ + { + comment: report.extraNotes, + convID: conversationIDKey, + includeTranscript: report.includeTranscript && !!conversationIDKey, + reason: report.reason, + username, + }, + S.waitingKeyUsersReportUser, + ], + () => {}, + () => {} + ) } const setConversationStatus = Chat.useChatContext(s => s.dispatch.blockConversation) - const _setUserBlocks = useUsersState(s => s.dispatch.setUserBlocks) const setUserBlocks = (newBlocks: NewBlocksMap) => { // Convert our state block array to action payload. const blocks = [...newBlocks.entries()] @@ -169,7 +178,7 @@ const Container = function BlockModal(ownProps: OwnProps) { username, })) if (blocks.length) { - _setUserBlocks(blocks) + setUserBlocksRPC([{blocks}, S.waitingKeyUsersSetUserBlocks], () => {}, () => {}) } } diff --git a/shared/stores/tests/wallets.test.ts b/shared/stores/tests/wallets.test.ts deleted file mode 100644 index 9f5fb76ca70e..000000000000 --- a/shared/stores/tests/wallets.test.ts +++ /dev/null @@ -1,68 +0,0 @@ -/// -import {resetAllStores} from '../../util/zustand' -import {useConfigState} from '../config' -import {useState as useWalletsState} from '../wallets' -import * as T from '../../constants/types' - -const mockLocalGetWalletAccountsLocalRpcPromise = jest.fn() -const mockLocalDeleteWalletAccountLocalRpcPromise = jest.fn() - -beforeEach(() => { - useConfigState.setState({loggedIn: true}) -}) - -afterEach(() => { - mockLocalGetWalletAccountsLocalRpcPromise.mockReset() - mockLocalDeleteWalletAccountLocalRpcPromise.mockReset() - jest.restoreAllMocks() - resetAllStores() -}) - -test('load populates the wallet account map when logged in', async () => { - mockLocalGetWalletAccountsLocalRpcPromise.mockResolvedValue([ - { - accountID: 'acct-1', - balanceDescription: '1.00 XLM', - deviceReadOnly: false, - isDefault: true, - name: 'Primary', - }, - ]) - jest.spyOn(T.RPCStellar, 'localGetWalletAccountsLocalRpcPromise').mockImplementation( - mockLocalGetWalletAccountsLocalRpcPromise - ) - - useWalletsState.getState().dispatch.load() - await new Promise(resolve => setImmediate(resolve)) - - expect(mockLocalGetWalletAccountsLocalRpcPromise).toHaveBeenCalled() - expect(useWalletsState.getState().accountMap.get('acct-1')).toMatchObject({ - accountID: 'acct-1', - balanceDescription: '1.00 XLM', - deviceReadOnly: false, - isDefault: true, - name: 'Primary', - }) -}) - -test('removeAccount deletes then reloads the wallet account list', async () => { - mockLocalGetWalletAccountsLocalRpcPromise.mockResolvedValue([]) - mockLocalDeleteWalletAccountLocalRpcPromise.mockResolvedValue(undefined) - jest.spyOn(T.RPCStellar, 'localGetWalletAccountsLocalRpcPromise').mockImplementation( - mockLocalGetWalletAccountsLocalRpcPromise - ) - jest.spyOn(T.RPCStellar, 'localDeleteWalletAccountLocalRpcPromise').mockImplementation( - mockLocalDeleteWalletAccountLocalRpcPromise - ) - - useWalletsState.getState().dispatch.removeAccount('acct-1') - await new Promise(resolve => setImmediate(resolve)) - await new Promise(resolve => setImmediate(resolve)) - await new Promise(resolve => setImmediate(resolve)) - - expect(mockLocalDeleteWalletAccountLocalRpcPromise).toHaveBeenCalledWith( - {accountID: 'acct-1', userAcknowledged: 'yes'}, - expect.any(String) - ) - expect(mockLocalGetWalletAccountsLocalRpcPromise).toHaveBeenCalledTimes(1) -}) diff --git a/shared/stores/users.tsx b/shared/stores/users.tsx index 21772be0c1ef..87bfffbb38ce 100644 --- a/shared/stores/users.tsx +++ b/shared/stores/users.tsx @@ -21,16 +21,8 @@ export type State = Store & { getBio: (username: string) => void getBlockState: (usernames: ReadonlyArray) => void onEngineIncomingImpl: (action: EngineGen.Actions) => void - reportUser: (p: { - username: string - reason: string - comment: string - includeTranscript: boolean - conversationIDKey?: string - }) => void resetState: () => void replace: (infoMap: State['infoMap'], blockMap?: State['blockMap']) => void - setUserBlocks: (blocks: ReadonlyArray) => void updates: (infos: ReadonlyArray<{name: string; info: Partial}>) => void } } @@ -107,31 +99,7 @@ export const useUsersState = Z.createZustand('users', (set, get) => { } }) }, - reportUser: p => { - const {conversationIDKey, username, reason, comment, includeTranscript} = p - const f = async () => { - await T.RPCGen.userReportUserRpcPromise( - { - comment, - convID: conversationIDKey, - includeTranscript, - reason, - username, - }, - S.waitingKeyUsersReportUser - ) - } - ignorePromise(f()) - }, resetState: Z.defaultReset, - setUserBlocks: blocks => { - const f = async () => { - if (blocks.length) { - await T.RPCGen.userSetUserBlocksRpcPromise({blocks}, S.waitingKeyUsersSetUserBlocks) - } - } - ignorePromise(f()) - }, updates: infos => { set(s => { for (const {name, info: i} of infos) { diff --git a/shared/stores/wallets.tsx b/shared/stores/wallets.tsx deleted file mode 100644 index ac1362775024..000000000000 --- a/shared/stores/wallets.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import * as T from '@/constants/types' -import {ignorePromise} from '@/constants/utils' -import * as Z from '@/util/zustand' -import {loadAccountsWaitingKey} from '@/constants/strings' -import {useConfigState} from '@/stores/config' - -export {loadAccountsWaitingKey} from '@/constants/strings' - -export type Account = { - accountID: string - balanceDescription: string - deviceReadOnly: boolean - isDefault: boolean - name: string -} - -type Store = T.Immutable<{ - accountMap: Map -}> - -const initialStore: Store = { - accountMap: new Map(), -} - -type State = Store & { - dispatch: { - load: () => void - removeAccount: (accountID: string) => void - resetState: () => void - } -} -export const useState = Z.createZustand('wallets', (set, get) => { - const dispatch: State['dispatch'] = { - load: () => { - const f = async () => { - if (!useConfigState.getState().loggedIn) { - return - } - const res = await T.RPCStellar.localGetWalletAccountsLocalRpcPromise(undefined, [ - loadAccountsWaitingKey, - ]) - set(s => { - s.accountMap = new Map( - res?.map(a => { - return [ - a.accountID, - { - accountID: a.accountID, - balanceDescription: a.balanceDescription, - deviceReadOnly: a.deviceReadOnly, - isDefault: a.isDefault, - name: a.name, - }, - ] - }) - ) - }) - } - ignorePromise(f()) - }, - removeAccount: accountID => { - const f = async () => { - await T.RPCStellar.localDeleteWalletAccountLocalRpcPromise( - {accountID, userAcknowledged: 'yes'}, - loadAccountsWaitingKey - ) - get().dispatch.load() - } - ignorePromise(f()) - }, - resetState: Z.defaultReset, - } - return { - ...initialStore, - dispatch, - } -}) diff --git a/shared/teams/channel/rows.tsx b/shared/teams/channel/rows.tsx index 1f22e61f3744..9bfa5759f70a 100644 --- a/shared/teams/channel/rows.tsx +++ b/shared/teams/channel/rows.tsx @@ -1,7 +1,7 @@ import * as C from '@/constants' import * as Chat from '@/stores/chat' import * as Teams from '@/stores/teams' -import type * as T from '@/constants/types' +import * as T from '@/constants/types' import * as Kb from '@/common-adapters' import MenuHeader from '../team/rows/menu-header.new' import {useUsersState} from '@/stores/users' @@ -33,12 +33,8 @@ const crownIcon = (roleType: T.Teams.TeamRoleType) => { const ChannelMemberRow = (props: Props) => { const {conversationIDKey, teamID, username} = props - const {infoMap, setUserBlocks} = useUsersState( - C.useShallow(s => ({ - infoMap: s.infoMap, - setUserBlocks: s.dispatch.setUserBlocks, - })) - ) + const infoMap = useUsersState(s => s.infoMap) + const setUserBlocks = C.useRPC(T.RPCGen.userSetUserBlocksRpcPromise) const participantInfo = Chat.useConvoState(conversationIDKey, s => s.participants) const teamsState = Teams.useTeamsState( C.useShallow(s => ({ @@ -56,7 +52,14 @@ const ChannelMemberRow = (props: Props) => { const roleType = teamMemberInfo.type const crown = (() => { const type = crownIcon(roleType) - return active && type ? : null + return active && type ? ( + + ) : null })() const fullNameLabel = fullname && active ? ( @@ -128,85 +131,94 @@ const ChannelMemberRow = (props: Props) => { ) const makePopup = (p: Kb.Popup2Parms) => { - const {attachTo, hidePopup} = p - const onOpenProfile = () => username && navToProfile(username) - const onRemoveFromChannel = () => - navigateAppend({ - name: 'teamReallyRemoveChannelMember', - params: {conversationIDKey, members: [username], teamID}, - }) - const onBlock = () => { - username && - setUserBlocks([ + const {attachTo, hidePopup} = p + const onOpenProfile = () => username && navToProfile(username) + const onRemoveFromChannel = () => + navigateAppend({ + name: 'teamReallyRemoveChannelMember', + params: {conversationIDKey, members: [username], teamID}, + }) + const onBlock = () => { + username && + setUserBlocks( + [ { - setChatBlock: true, - setFollowBlock: true, - username, + blocks: [ + { + setChatBlock: true, + setFollowBlock: true, + username, + }, + ], }, - ]) - } + C.waitingKeyUsersSetUserBlocks, + ], + () => {}, + () => {} + ) + } - const menuItems: Kb.MenuItems = [ - 'Divider', - ...(yourOperations.manageMembers - ? ([ - { - icon: 'iconfont-chat', - onClick: () => - navigateAppend({name: 'teamAddToChannels', params: {teamID, usernames: [username]}}), - title: 'Add to channels...', - }, - {icon: 'iconfont-crown-admin', onClick: onEditMember, title: 'Edit role...'}, - ] as Kb.MenuItems) - : []), - {icon: 'iconfont-person', onClick: onOpenProfile, title: 'View profile'}, - {icon: 'iconfont-chat', onClick: onChat, title: 'Chat'}, - ...(yourOperations.manageMembers || !isYou ? (['Divider'] as Kb.MenuItems) : []), - ...((yourOperations.manageMembers || isYou) && !props.isGeneral - ? ([ - { - danger: true, - icon: 'iconfont-remove', - onClick: onRemoveFromChannel, - title: 'Remove from channel', - }, - ] as Kb.MenuItems) - : []), - ...(!isYou - ? ([ - { - danger: true, - icon: 'iconfont-user-block', - onClick: onBlock, - title: 'Block', - }, - ] as Kb.MenuItems) - : []), - ] - const menuHeader = ( - - {crown} - {roleLabel} - - } - /> - ) + const menuItems: Kb.MenuItems = [ + 'Divider', + ...(yourOperations.manageMembers + ? ([ + { + icon: 'iconfont-chat', + onClick: () => + navigateAppend({name: 'teamAddToChannels', params: {teamID, usernames: [username]}}), + title: 'Add to channels...', + }, + {icon: 'iconfont-crown-admin', onClick: onEditMember, title: 'Edit role...'}, + ] as Kb.MenuItems) + : []), + {icon: 'iconfont-person', onClick: onOpenProfile, title: 'View profile'}, + {icon: 'iconfont-chat', onClick: onChat, title: 'Chat'}, + ...(yourOperations.manageMembers || !isYou ? (['Divider'] as Kb.MenuItems) : []), + ...((yourOperations.manageMembers || isYou) && !props.isGeneral + ? ([ + { + danger: true, + icon: 'iconfont-remove', + onClick: onRemoveFromChannel, + title: 'Remove from channel', + }, + ] as Kb.MenuItems) + : []), + ...(!isYou + ? ([ + { + danger: true, + icon: 'iconfont-user-block', + onClick: onBlock, + title: 'Block', + }, + ] as Kb.MenuItems) + : []), + ] + const menuHeader = ( + + {crown} + {roleLabel} + + } + /> + ) - return ( - - ) - } + return ( + + ) + } const {showPopup, popupAnchor, popup} = Kb.usePopup2(makePopup) diff --git a/shared/teams/team/rows/member-row.tsx b/shared/teams/team/rows/member-row.tsx index f0a04c7fedb8..fe3e7cb487c5 100644 --- a/shared/teams/team/rows/member-row.tsx +++ b/shared/teams/team/rows/member-row.tsx @@ -2,11 +2,10 @@ import * as C from '@/constants' import * as Chat from '@/stores/chat' import * as Kb from '@/common-adapters' import * as Teams from '@/stores/teams' -import type * as T from '@/constants/types' +import * as T from '@/constants/types' import MenuHeader from './menu-header.new' import {useSafeNavigation} from '@/util/safe-navigation' import {useTrackerState} from '@/stores/tracker' -import {useUsersState} from '@/stores/users' import {useCurrentUserState} from '@/stores/current-user' import {navToProfile} from '@/constants/router' @@ -48,14 +47,15 @@ export const TeamMemberRow = (props: Props) => { const {roleType, fullName, username, youCanManageMembers} = props const {onOpenProfile, onChat, onBlock, onRemoveFromTeam} = props const active = props.status === 'active' - const crown = active && showCrown[roleType] ? ( - - ) : null + const crown = + active && showCrown[roleType] ? ( + + ) : null const fullNameLabel = fullName && active ? ( @@ -88,8 +88,8 @@ export const TeamMemberRow = (props: Props) => { const setMemberSelected = Teams.useTeamsState(s => s.dispatch.setMemberSelected) const onSelect = (selected: boolean) => { - setMemberSelected(teamID, props.username, selected) - } + setMemberSelected(teamID, props.username, selected) + } const canEnterMemberPage = props.youCanManageMembers && active && !props.needsPUK const pOnClick = props.onClick @@ -137,71 +137,71 @@ export const TeamMemberRow = (props: Props) => { ) const makePopup = (p: Kb.Popup2Parms) => { - const {attachTo, hidePopup} = p - const menuHeader = ( - - {crown} - {roleLabel} - - } - /> - ) + const {attachTo, hidePopup} = p + const menuHeader = ( + + {crown} + {roleLabel} + + } + /> + ) - const menuItems: Kb.MenuItems = [ - 'Divider', - ...(youCanManageMembers - ? ([ - { - icon: 'iconfont-chat', - onClick: () => - nav.safeNavigateAppend({ - name: 'teamAddToChannels', - params: {teamID, usernames: [username]}, - }), - title: 'Add to channels...', - }, - {icon: 'iconfont-crown-admin', onClick: onClick, title: 'Edit role...'}, - ] as Kb.MenuItems) - : []), - {icon: 'iconfont-person', onClick: onOpenProfile, title: 'View profile'}, - {icon: 'iconfont-chat', onClick: onChat, title: 'Chat'}, - ...(youCanManageMembers || !isYou ? (['Divider'] as Kb.MenuItems) : []), - ...(youCanManageMembers - ? ([ - { - danger: true, - icon: 'iconfont-remove', - onClick: onRemoveFromTeam, - title: 'Remove from team', - }, - ] as Kb.MenuItems) - : []), - ...(!isYou - ? ([ - { - danger: true, - icon: 'iconfont-block', - onClick: onBlock, - title: 'Block', - }, - ] as Kb.MenuItems) - : []), - ] - return ( - - ) - } + const menuItems: Kb.MenuItems = [ + 'Divider', + ...(youCanManageMembers + ? ([ + { + icon: 'iconfont-chat', + onClick: () => + nav.safeNavigateAppend({ + name: 'teamAddToChannels', + params: {teamID, usernames: [username]}, + }), + title: 'Add to channels...', + }, + {icon: 'iconfont-crown-admin', onClick: onClick, title: 'Edit role...'}, + ] as Kb.MenuItems) + : []), + {icon: 'iconfont-person', onClick: onOpenProfile, title: 'View profile'}, + {icon: 'iconfont-chat', onClick: onChat, title: 'Chat'}, + ...(youCanManageMembers || !isYou ? (['Divider'] as Kb.MenuItems) : []), + ...(youCanManageMembers + ? ([ + { + danger: true, + icon: 'iconfont-remove', + onClick: onRemoveFromTeam, + title: 'Remove from team', + }, + ] as Kb.MenuItems) + : []), + ...(!isYou + ? ([ + { + danger: true, + icon: 'iconfont-block', + onClick: onBlock, + title: 'Block', + }, + ] as Kb.MenuItems) + : []), + ] + return ( + + ) + } const {showPopup, popupAnchor, popup} = Kb.usePopup2(makePopup) const actions = ( @@ -304,9 +304,14 @@ const Container = (ownProps: OwnProps) => { const status = info.status const waitingForAdd = C.Waiting.useAnyWaiting(C.waitingKeyTeamsAddMember(teamID, username)) const waitingForRemove = C.Waiting.useAnyWaiting(C.waitingKeyTeamsRemoveMember(teamID, username)) - const setUserBlocks = useUsersState(s => s.dispatch.setUserBlocks) + const setUserBlocks = C.useRPC(T.RPCGen.userSetUserBlocksRpcPromise) const onBlock = () => { - username && setUserBlocks([{setChatBlock: true, setFollowBlock: true, username}]) + username && + setUserBlocks( + [{blocks: [{setChatBlock: true, setFollowBlock: true, username}]}, C.waitingKeyUsersSetUserBlocks], + () => {}, + () => {} + ) } const previewConversation = Chat.useChatState(s => s.dispatch.previewConversation) const onChat = () => { diff --git a/shared/wallets/account-utils.test.ts b/shared/wallets/account-utils.test.ts new file mode 100644 index 000000000000..9bdd2df02042 --- /dev/null +++ b/shared/wallets/account-utils.test.ts @@ -0,0 +1,99 @@ +/// +import * as T from '@/constants/types' +import { + makeReallyRemoveAccountRouteParams, + makeRemoveAccountRouteParams, + sortAccounts, + toAccount, + type Account, +} from './account-utils' + +const makeRPCAccount = ( + overrides?: Partial +): T.RPCStellar.WalletAccountLocal => ({ + accountID: 'acct-1' as T.RPCStellar.AccountID, + accountMode: T.RPCStellar.AccountMode.user, + accountModeEditable: true, + balanceDescription: '1.00 XLM', + canAddTrustline: false, + canSubmitTx: true, + currencyLocal: { + code: 'USD', + description: 'United States Dollar', + name: 'USD', + symbol: '$', + }, + deviceReadOnly: false, + isDefault: true, + isFunded: true, + name: 'Primary', + seqno: '1', + ...overrides, +}) + +const makeAccount = (overrides?: Partial): Account => ({ + accountID: 'acct-1' as T.RPCStellar.AccountID, + balanceDescription: '1.00 XLM', + deviceReadOnly: false, + isDefault: false, + name: 'Primary', + ...overrides, +}) + +test('toAccount keeps only the wallet fields used by the UI', () => { + expect( + toAccount( + makeRPCAccount({ + accountID: 'acct-2' as T.RPCStellar.AccountID, + balanceDescription: '7.25 XLM', + deviceReadOnly: true, + isDefault: false, + name: 'Savings', + }) + ) + ).toEqual({ + accountID: 'acct-2', + balanceDescription: '7.25 XLM', + deviceReadOnly: true, + isDefault: false, + name: 'Savings', + }) +}) + +test('sortAccounts keeps the default account first, then sorts remaining accounts by name', () => { + expect( + sortAccounts([ + makeAccount({accountID: 'acct-2' as T.RPCStellar.AccountID, name: 'Zulu'}), + makeAccount({accountID: 'acct-3' as T.RPCStellar.AccountID, isDefault: true, name: 'Middle'}), + makeAccount({accountID: 'acct-1' as T.RPCStellar.AccountID, name: 'Alpha'}), + ]).map(a => a.accountID) + ).toEqual(['acct-3', 'acct-1', 'acct-2']) +}) + +test('remove-account route params carry the account fields the confirmation modal needs', () => { + expect( + makeRemoveAccountRouteParams( + makeAccount({ + accountID: 'acct-9' as T.RPCStellar.AccountID, + balanceDescription: '9.99 XLM', + name: 'Vacation', + }) + ) + ).toEqual({ + accountID: 'acct-9', + balanceDescription: '9.99 XLM', + name: 'Vacation', + }) +}) + +test('really-remove-account route params only carry the fields used on the final screen', () => { + expect( + makeReallyRemoveAccountRouteParams({ + accountID: 'acct-5' as T.RPCStellar.AccountID, + name: 'Emergency', + }) + ).toEqual({ + accountID: 'acct-5', + name: 'Emergency', + }) +}) diff --git a/shared/wallets/account-utils.ts b/shared/wallets/account-utils.ts new file mode 100644 index 000000000000..5a49b3ce7c27 --- /dev/null +++ b/shared/wallets/account-utils.ts @@ -0,0 +1,32 @@ +import type * as T from '@/constants/types' + +export type Account = Pick< + T.RPCStellar.WalletAccountLocal, + 'accountID' | 'balanceDescription' | 'deviceReadOnly' | 'isDefault' | 'name' +> + +export const toAccount = (account: T.RPCStellar.WalletAccountLocal): Account => ({ + accountID: account.accountID, + balanceDescription: account.balanceDescription, + deviceReadOnly: account.deviceReadOnly, + isDefault: account.isDefault, + name: account.name, +}) + +export const sortAccounts = (accounts: ReadonlyArray): Array => + [...accounts].sort((a, b) => { + if (a.isDefault) return -1 + if (b.isDefault) return 1 + return a.name < b.name ? -1 : 1 + }) + +export const makeRemoveAccountRouteParams = (account: Account) => ({ + accountID: account.accountID, + balanceDescription: account.balanceDescription, + name: account.name, +}) + +export const makeReallyRemoveAccountRouteParams = (account: Pick) => ({ + accountID: account.accountID, + name: account.name, +}) diff --git a/shared/wallets/index.tsx b/shared/wallets/index.tsx index e6e683d9a552..3ad040d12cfb 100644 --- a/shared/wallets/index.tsx +++ b/shared/wallets/index.tsx @@ -2,10 +2,10 @@ import * as C from '@/constants' import * as React from 'react' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' -import * as Wallets from '@/stores/wallets' -import {useState as useWalletsState} from '@/stores/wallets' +import {loadAccountsWaitingKey} from '@/constants/strings' +import {makeRemoveAccountRouteParams, sortAccounts, toAccount, type Account} from './account-utils' -const Row = (p: {account: Wallets.Account}) => { +const Row = (p: {account: Account}) => { const {account} = p const {name, accountID, deviceReadOnly, balanceDescription, isDefault} = account const [sk, setSK] = React.useState('') @@ -13,7 +13,7 @@ const Row = (p: {account: Wallets.Account}) => { const getSecretKey = C.useRPC(T.RPCStellar.localGetWalletAccountSecretKeyLocalRpcPromise) const navigateAppend = C.useRouterState(s => s.dispatch.navigateAppend) const onRemove = () => { - navigateAppend({name: 'removeAccount', params: {accountID}}) + navigateAppend({name: 'removeAccount', params: makeRemoveAccountRouteParams(account)}) } const onCopied = () => { setSK('') @@ -111,16 +111,24 @@ const Row = (p: {account: Wallets.Account}) => { } const Container = () => { + const [accounts, setAccounts] = React.useState>([]) const [acceptedDisclaimer, setAcceptedDisclaimer] = React.useState(false) const checkDisclaimer = C.useRPC(T.RPCStellar.localHasAcceptedDisclaimerLocalRpcPromise) - - const load = useWalletsState(s => s.dispatch.load) + const loadAccounts = C.useRPC(T.RPCStellar.localGetWalletAccountsLocalRpcPromise) C.Router2.useSafeFocusEffect( () => { - load() + loadAccounts( + [undefined, loadAccountsWaitingKey], + res => { + setAccounts((res ?? []).map(toAccount)) + }, + () => { + setAccounts([]) + } + ) checkDisclaimer( - [undefined, Wallets.loadAccountsWaitingKey], + [undefined, loadAccountsWaitingKey], r => { setAcceptedDisclaimer(r) }, @@ -132,16 +140,11 @@ const Container = () => { } ) - const accountMap = useWalletsState(s => s.accountMap) - const accounts = [...accountMap.values()].sort((a, b) => { - if (a.isDefault) return -1 - if (b.isDefault) return 1 - return a.name < b.name ? -1 : 1 - }) + const sortedAccounts = sortAccounts(accounts) - const loading = C.Waiting.useAnyWaiting(Wallets.loadAccountsWaitingKey) + const loading = C.Waiting.useAnyWaiting(loadAccountsWaitingKey) - const rows = accounts.map((a, idx) => ) + const rows = sortedAccounts.map(a => ) return ( diff --git a/shared/wallets/really-remove-account.tsx b/shared/wallets/really-remove-account.tsx index 00db123920c0..06c37f3755a0 100644 --- a/shared/wallets/really-remove-account.tsx +++ b/shared/wallets/really-remove-account.tsx @@ -3,16 +3,17 @@ import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import * as React from 'react' import WalletPopup from './wallet-popup' -import * as Wallets from '@/stores/wallets' -import {useState as useWalletsState} from '@/stores/wallets' +import {loadAccountsWaitingKey} from '@/constants/strings' import {useConfigState} from '@/stores/config' -type OwnProps = {accountID: string} +type OwnProps = { + accountID: string + name: string +} const ReallyRemoveAccountPopup = (props: OwnProps) => { - const {accountID} = props - const waiting = C.Waiting.useAnyWaiting(Wallets.loadAccountsWaitingKey) - const name = useWalletsState(s => s.accountMap.get(accountID)?.name) ?? '' + const {accountID, name} = props + const waiting = C.Waiting.useAnyWaiting(loadAccountsWaitingKey) const [showingToast, setShowToast] = React.useState(false) const attachmentRef = React.useRef(null) const setShowToastFalseLater = Kb.useTimeout(() => setShowToast(false), 2000) @@ -22,11 +23,12 @@ const ReallyRemoveAccountPopup = (props: OwnProps) => { const [sk, setSK] = React.useState('') const loading = !sk const getSecretKey = C.useRPC(T.RPCStellar.localGetWalletAccountSecretKeyLocalRpcPromise) + const deleteAccount = C.useRPC(T.RPCStellar.localDeleteWalletAccountLocalRpcPromise) const navigateUp = C.useRouterState(s => s.dispatch.navigateUp) - const removeAccount = useWalletsState(s => s.dispatch.removeAccount) const onFinish = () => { - removeAccount(accountID) - navigateUp() + deleteAccount([{accountID, userAcknowledged: 'yes'}, loadAccountsWaitingKey], () => { + navigateUp() + }, () => {}) } React.useEffect(() => { diff --git a/shared/wallets/remove-account.tsx b/shared/wallets/remove-account.tsx index d85a140ba9e9..533ce4426a8a 100644 --- a/shared/wallets/remove-account.tsx +++ b/shared/wallets/remove-account.tsx @@ -1,22 +1,26 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' import WalletPopup from './wallet-popup' -import {useState as useWalletsState} from '@/stores/wallets' +import {makeReallyRemoveAccountRouteParams} from './account-utils' -type OwnProps = {accountID: string} +type OwnProps = { + accountID: string + balanceDescription: string + name: string +} const Container = (ownProps: OwnProps) => { - const {accountID} = ownProps - const account = useWalletsState(s => s.accountMap.get(accountID)) - const balance = account?.balanceDescription ?? 'Error loading account' - const name = account?.name ?? '' + const {accountID, balanceDescription, name} = ownProps const navigateUp = C.useRouterState(s => s.dispatch.navigateUp) const onClose = () => { navigateUp() } const navigateAppend = C.useRouterState(s => s.dispatch.navigateAppend) const onDelete = () => { - navigateAppend({name: 'reallyRemoveAccount', params: {accountID}}, true) + navigateAppend( + {name: 'reallyRemoveAccount', params: makeReallyRemoveAccountRouteParams({accountID, name})}, + true + ) } const buttons = [ @@ -27,7 +31,6 @@ const Container = (ownProps: OwnProps) => { label="Yes, remove" onClick={onDelete} type="Danger" - disabled={!account} />, ] @@ -55,7 +58,7 @@ const Container = (ownProps: OwnProps) => { from Keybase, but you can still use it elsewhere if you save the private key. Balance: - {balance} + {balanceDescription} ) @@ -78,5 +81,3 @@ const styles = Kb.Styles.styleSheetCreate(() => ({ })) export default Container - - diff --git a/skill/zustand-store-pruning/references/store-checklist.md b/skill/zustand-store-pruning/references/store-checklist.md index 6c133d0d5da5..656fb34fd0ff 100644 --- a/skill/zustand-store-pruning/references/store-checklist.md +++ b/skill/zustand-store-pruning/references/store-checklist.md @@ -35,11 +35,11 @@ Status: - [x] `settings-password` kept only `randomPW` in store; moved submit/load flows into settings screens - [x] `settings-phone` kept notification-backed `phones` and `addedPhone`; moved add/verify/default-country flow into local hooks and route params - [x] `signup` removed the store; People now owns the one-shot welcome banner helper and signup keeps device-name draft state in feature-local helpers -- [ ] `team-building` -- [ ] `tracker` +- [~] `team-building` already runs as a per-namespace provider-scoped flow store; defer unless we want a narrow cleanup of the currently unused `selectedRole` / `sendNotification` state +- [x] `tracker` kept identify notification plumbing, shared profile and non-user caches, proof suggestions, and desktop remote tracker window state in store - [x] `unlock-folders` removed dead phase/device state; kept only engine callback forwarding into `config` -- [ ] `users` -- [ ] `wallets` +- [x] `users` kept shared fullname/bio/broken/block caches plus identify and block notifications in store; moved block/report RPC wrappers into chat and teams components +- [x] `wallets` removed the store; wallet account loading and removal now live in wallet screens, and removal modals use explicit route params instead of hidden store reads ## Larger / More Global Stores