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