From f2af60b54da28451a209e92ba3860c89b9b977df Mon Sep 17 00:00:00 2001 From: Randy Schott <1815175+schottra@users.noreply.github.com> Date: Thu, 25 Apr 2024 12:30:14 -0400 Subject: [PATCH 01/14] basic grants fetching to determine if picker should be shown --- packages/common/src/api/account.ts | 13 ++++- .../components/nav/desktop/AccountDetails.tsx | 5 ++ .../AccountSwitcher/AccountSwitcher.tsx | 47 +++++++++++++++++++ 3 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 packages/web/src/components/nav/desktop/AccountSwitcher/AccountSwitcher.tsx diff --git a/packages/common/src/api/account.ts b/packages/common/src/api/account.ts index 7cb5e092795..caf66af04eb 100644 --- a/packages/common/src/api/account.ts +++ b/packages/common/src/api/account.ts @@ -29,10 +29,21 @@ const accountApi = createApi({ options: { type: 'mutation' } + }, + getManagedAccounts: { + async fetch(_, context) { + const sdk = await context.audiusSdk() + const grants = await sdk.users.getUserGrants() + return grants.data ?? [] + }, + options: { + type: 'query' + } } } }) -export const { useGetCurrentUserId, useResetPassword } = accountApi.hooks +export const { useGetCurrentUserId, useResetPassword, useGetManagedAccounts } = + accountApi.hooks export const accountApiReducer = accountApi.reducer diff --git a/packages/web/src/components/nav/desktop/AccountDetails.tsx b/packages/web/src/components/nav/desktop/AccountDetails.tsx index 935e168f439..1974b11a148 100644 --- a/packages/web/src/components/nav/desktop/AccountDetails.tsx +++ b/packages/web/src/components/nav/desktop/AccountDetails.tsx @@ -7,6 +7,9 @@ import { useSelector } from 'utils/reducer' import { SIGN_IN_PAGE, profilePage } from 'utils/route' import styles from './AccountDetails.module.css' +import { FeatureFlags } from '@audius/common/services' +import { useFlag } from 'hooks/useRemoteConfig' +import { AccountSwitcher } from './AccountSwitcher/AccountSwitcher' const { getAccountUser } = accountSelectors @@ -17,6 +20,7 @@ const messages = { export const AccountDetails = () => { const account = useSelector((state) => getAccountUser(state)) + const { isEnabled: isManagerModeEnabled } = useFlag(FeatureFlags.MANAGER_MODE) const profileLink = profilePage(account?.handle ?? '') @@ -57,6 +61,7 @@ export const AccountDetails = () => { )} + {isManagerModeEnabled && account ? : null} ) diff --git a/packages/web/src/components/nav/desktop/AccountSwitcher/AccountSwitcher.tsx b/packages/web/src/components/nav/desktop/AccountSwitcher/AccountSwitcher.tsx new file mode 100644 index 00000000000..f9d9c36e037 --- /dev/null +++ b/packages/web/src/components/nav/desktop/AccountSwitcher/AccountSwitcher.tsx @@ -0,0 +1,47 @@ +import { + useGetManagedAccounts, + useGetUserIdFromWallet +} from '@audius/common/api' +import { useAudiusQueryContext } from '@audius/common/audius-query' +import { IconButton, IconCaretDown } from '@audius/harmony' +import { useEffect, useState } from 'react' + +const useEthWalletAddress = () => { + const { audiusBackend } = useAudiusQueryContext() + const [ethWalletAddress, setEthWalletAddress] = useState(null) + + useEffect(() => { + const fetchEthWalletAddress = async () => { + const libs = await audiusBackend.getAudiusLibsTyped() + const ethWalletAddress = await libs.web3Manager?.getWalletAddress() + if (!ethWalletAddress) { + console.error('Unexpected missing ethWalletAddress') + } + console.log(ethWalletAddress) + setEthWalletAddress(ethWalletAddress) + } + + fetchEthWalletAddress() + }, [audiusBackend]) + + return ethWalletAddress +} + +const useManagedAccounts = () => { + const ethWalletAddress = useEthWalletAddress() +} + +export const AccountSwitcher = () => { + const { data: managedAccounts } = useGetManagedAccounts({}) + const onClickExpander = () => {} + + return managedAccounts && managedAccounts.length ? ( + + ) : null +} From f22f100fef71183fd4f205bab2b1bd082e7c728d Mon Sep 17 00:00:00 2001 From: Randy Schott <1815175+schottra@users.noreply.github.com> Date: Fri, 26 Apr 2024 12:16:11 -0400 Subject: [PATCH 02/14] Use correct endpoint --- packages/common/src/api/account.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/common/src/api/account.ts b/packages/common/src/api/account.ts index caf66af04eb..c63e4e772ee 100644 --- a/packages/common/src/api/account.ts +++ b/packages/common/src/api/account.ts @@ -1,4 +1,5 @@ import { createApi } from '~/audius-query' +import { Id } from './utils' type ResetPasswordArgs = { email: string @@ -31,10 +32,13 @@ const accountApi = createApi({ } }, getManagedAccounts: { - async fetch(_, context) { - const sdk = await context.audiusSdk() - const grants = await sdk.users.getUserGrants() - return grants.data ?? [] + async fetch(_, { audiusSdk, audiusBackend }) { + const sdk = await audiusSdk() + const currentUserId = (await audiusBackend.getAccount())?.user_id + const managedUsers = await sdk.users.getManagedUsers({ + id: Id.parse(currentUserId) + }) + return managedUsers.data ?? [] }, options: { type: 'query' From 9d8f335a52fd9cd4c7dd2a65c4926a91ed4a7258 Mon Sep 17 00:00:00 2001 From: Randy Schott <1815175+schottra@users.noreply.github.com> Date: Mon, 29 Apr 2024 10:49:08 -0400 Subject: [PATCH 03/14] checkpoint --- packages/common/src/api/account.ts | 7 ++-- packages/common/src/audius-query/schema.ts | 5 +++ packages/common/src/models/Grant.ts | 23 +++++++++++++ packages/common/src/models/User.ts | 15 ++++++++- .../AccountSwitcher/AccountSwitcher.tsx | 32 ++----------------- 5 files changed, 50 insertions(+), 32 deletions(-) create mode 100644 packages/common/src/models/Grant.ts diff --git a/packages/common/src/api/account.ts b/packages/common/src/api/account.ts index c63e4e772ee..a680c80cc3d 100644 --- a/packages/common/src/api/account.ts +++ b/packages/common/src/api/account.ts @@ -38,10 +38,13 @@ const accountApi = createApi({ const managedUsers = await sdk.users.getManagedUsers({ id: Id.parse(currentUserId) }) - return managedUsers.data ?? [] + + const { data = [] } = managedUsers + return }, options: { - type: 'query' + type: 'query', + schemaKey: 'managedUsers' } } } diff --git a/packages/common/src/audius-query/schema.ts b/packages/common/src/audius-query/schema.ts index 767000cd8e9..b855749377f 100644 --- a/packages/common/src/audius-query/schema.ts +++ b/packages/common/src/audius-query/schema.ts @@ -22,7 +22,12 @@ export const collectionSchema = new schema.Entity( { idAttribute: 'playlist_id' } ) +export const managedUserSchema = new schema.Object({ + user: userSchema +}) + export const apiResponseSchema = new schema.Object({ + managedUsers: new schema.Array(managedUserSchema), user: userSchema, track: trackSchema, collection: collectionSchema, diff --git a/packages/common/src/models/Grant.ts b/packages/common/src/models/Grant.ts new file mode 100644 index 00000000000..fbcea36b91a --- /dev/null +++ b/packages/common/src/models/Grant.ts @@ -0,0 +1,23 @@ +import { Grant as SDKGrant } from '@audius/sdk' +import { Nullable, decodeHashId } from '~/utils' +import { ID } from './Identifiers' + +export type Grant = { + grantee_address: string + user_id: Nullable + is_revoked: boolean + is_approved: boolean + created_at: string + updated_at: string +} + +export const grantFromSDK = (input: SDKGrant): Grant => { + return { + grantee_address: input.granteeAddress, + user_id: decodeHashId(input.userId) ?? null, + is_revoked: input.isRevoked, + is_approved: input.isApproved, + created_at: input.createdAt, + updated_at: input.updatedAt + } +} diff --git a/packages/common/src/models/User.ts b/packages/common/src/models/User.ts index d45a46c2027..d05aa84347f 100644 --- a/packages/common/src/models/User.ts +++ b/packages/common/src/models/User.ts @@ -1,4 +1,4 @@ -import { full } from '@audius/sdk' +import { full, ManagedUser } from '@audius/sdk' import { omit } from 'lodash' import snakecaseKeys from 'snakecase-keys' @@ -23,6 +23,7 @@ import { Nullable, removeNullable } from '~/utils/typeUtils' import { Timestamped } from './Timestamped' import { UserEvent } from './UserEvent' +import { Grant, grantFromSDK } from './Grant' export type UserMetadata = { album_count: number @@ -85,6 +86,11 @@ export type UserMetadata = { events?: UserEvent } & Timestamped +export type ManagedUserMetadata = { + grant: Grant + user: UserMetadata +} + export type ComputedUserProperties = { _profile_picture_sizes: ProfilePictureSizes _cover_photo_sizes: CoverPhotoSizes @@ -164,3 +170,10 @@ export const userMetadataFromSDK = ( export const userMetadataListFromSDK = (input?: full.UserFull[]) => input ? input.map((d) => userMetadataFromSDK(d)).filter(removeNullable) : [] + +export const managedUserFromSDK = (input: ManagedUser): ManagedUserMetadata => { + return { + grant: grantFromSDK(input.grant), + user: userMetadataFromSDK(input.user) as UserMetadata + } +} diff --git a/packages/web/src/components/nav/desktop/AccountSwitcher/AccountSwitcher.tsx b/packages/web/src/components/nav/desktop/AccountSwitcher/AccountSwitcher.tsx index f9d9c36e037..775b835fd21 100644 --- a/packages/web/src/components/nav/desktop/AccountSwitcher/AccountSwitcher.tsx +++ b/packages/web/src/components/nav/desktop/AccountSwitcher/AccountSwitcher.tsx @@ -1,40 +1,14 @@ -import { - useGetManagedAccounts, - useGetUserIdFromWallet -} from '@audius/common/api' +import { useGetManagedAccounts } from '@audius/common/api' import { useAudiusQueryContext } from '@audius/common/audius-query' import { IconButton, IconCaretDown } from '@audius/harmony' import { useEffect, useState } from 'react' -const useEthWalletAddress = () => { - const { audiusBackend } = useAudiusQueryContext() - const [ethWalletAddress, setEthWalletAddress] = useState(null) - - useEffect(() => { - const fetchEthWalletAddress = async () => { - const libs = await audiusBackend.getAudiusLibsTyped() - const ethWalletAddress = await libs.web3Manager?.getWalletAddress() - if (!ethWalletAddress) { - console.error('Unexpected missing ethWalletAddress') - } - console.log(ethWalletAddress) - setEthWalletAddress(ethWalletAddress) - } - - fetchEthWalletAddress() - }, [audiusBackend]) - - return ethWalletAddress -} - -const useManagedAccounts = () => { - const ethWalletAddress = useEthWalletAddress() -} - export const AccountSwitcher = () => { const { data: managedAccounts } = useGetManagedAccounts({}) const onClickExpander = () => {} + console.log(managedAccounts) + return managedAccounts && managedAccounts.length ? ( Date: Mon, 29 Apr 2024 11:58:31 -0400 Subject: [PATCH 04/14] use full endpoint --- packages/common/src/api/account.ts | 5 +++-- packages/common/src/models/User.ts | 6 ++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/common/src/api/account.ts b/packages/common/src/api/account.ts index a680c80cc3d..e49a4236eb6 100644 --- a/packages/common/src/api/account.ts +++ b/packages/common/src/api/account.ts @@ -1,5 +1,6 @@ import { createApi } from '~/audius-query' import { Id } from './utils' +import { managedUserFromSDK } from '~/models' type ResetPasswordArgs = { email: string @@ -35,12 +36,12 @@ const accountApi = createApi({ async fetch(_, { audiusSdk, audiusBackend }) { const sdk = await audiusSdk() const currentUserId = (await audiusBackend.getAccount())?.user_id - const managedUsers = await sdk.users.getManagedUsers({ + const managedUsers = await sdk.full.users.getManagedUsers({ id: Id.parse(currentUserId) }) const { data = [] } = managedUsers - return + return data.map(managedUserFromSDK) }, options: { type: 'query', diff --git a/packages/common/src/models/User.ts b/packages/common/src/models/User.ts index d05aa84347f..ee0dd556edc 100644 --- a/packages/common/src/models/User.ts +++ b/packages/common/src/models/User.ts @@ -1,4 +1,4 @@ -import { full, ManagedUser } from '@audius/sdk' +import { full } from '@audius/sdk' import { omit } from 'lodash' import snakecaseKeys from 'snakecase-keys' @@ -171,7 +171,9 @@ export const userMetadataFromSDK = ( export const userMetadataListFromSDK = (input?: full.UserFull[]) => input ? input.map((d) => userMetadataFromSDK(d)).filter(removeNullable) : [] -export const managedUserFromSDK = (input: ManagedUser): ManagedUserMetadata => { +export const managedUserFromSDK = ( + input: full.ManagedUser +): ManagedUserMetadata => { return { grant: grantFromSDK(input.grant), user: userMetadataFromSDK(input.user) as UserMetadata From 83e95d596eb4f9b278b7ed4d03d338fb9380deb9 Mon Sep 17 00:00:00 2001 From: Randy Schott <1815175+schottra@users.noreply.github.com> Date: Mon, 29 Apr 2024 16:39:30 -0400 Subject: [PATCH 05/14] add managers endpoint --- packages/common/src/api/account.ts | 24 +++++++++++++++++++-- packages/common/src/models/User.ts | 34 +++++++++++++++++++++++++++--- 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/packages/common/src/api/account.ts b/packages/common/src/api/account.ts index e49a4236eb6..817b177997a 100644 --- a/packages/common/src/api/account.ts +++ b/packages/common/src/api/account.ts @@ -1,6 +1,6 @@ import { createApi } from '~/audius-query' import { Id } from './utils' -import { managedUserFromSDK } from '~/models' +import { managedUserListFromSDK, userManagerListFromSDK } from '~/models' type ResetPasswordArgs = { email: string @@ -35,13 +35,33 @@ const accountApi = createApi({ getManagedAccounts: { async fetch(_, { audiusSdk, audiusBackend }) { const sdk = await audiusSdk() + // TODO: Look this up in a better way + // https://linear.app/audius/issue/PAY-2816/look-up-parentchild-ids-from-a-better-place const currentUserId = (await audiusBackend.getAccount())?.user_id const managedUsers = await sdk.full.users.getManagedUsers({ id: Id.parse(currentUserId) }) const { data = [] } = managedUsers - return data.map(managedUserFromSDK) + return managedUserListFromSDK(data) + }, + options: { + type: 'query', + schemaKey: 'managedUsers' + } + }, + getManagers: { + async fetch(_, { audiusSdk, audiusBackend }) { + const sdk = await audiusSdk() + // TODO: Look this up in a better way + // https://linear.app/audius/issue/PAY-2816/look-up-parentchild-ids-from-a-better-place + const currentUserId = (await audiusBackend.getAccount())?.user_id + const managedUsers = await sdk.full.users.getManagers({ + id: Id.parse(currentUserId) + }) + + const { data = [] } = managedUsers + return userManagerListFromSDK(data) }, options: { type: 'query', diff --git a/packages/common/src/models/User.ts b/packages/common/src/models/User.ts index ee0dd556edc..b0186f6616e 100644 --- a/packages/common/src/models/User.ts +++ b/packages/common/src/models/User.ts @@ -91,6 +91,11 @@ export type ManagedUserMetadata = { user: UserMetadata } +export type UserManagerMetadata = { + grant: Grant + manager: UserMetadata +} + export type ComputedUserProperties = { _profile_picture_sizes: ProfilePictureSizes _cover_photo_sizes: CoverPhotoSizes @@ -173,9 +178,32 @@ export const userMetadataListFromSDK = (input?: full.UserFull[]) => export const managedUserFromSDK = ( input: full.ManagedUser -): ManagedUserMetadata => { +): ManagedUserMetadata | undefined => { + const user = userMetadataFromSDK(input.user) + if (!user) { + return undefined + } return { - grant: grantFromSDK(input.grant), - user: userMetadataFromSDK(input.user) as UserMetadata + user, + grant: grantFromSDK(input.grant) } } + +export const managedUserListFromSDK = (input?: full.ManagedUser[]) => + input ? input.map((d) => managedUserFromSDK(d)).filter(removeNullable) : [] + +export const userManagerFromSDK = ( + input: full.UserManager +): UserManagerMetadata | undefined => { + const manager = userMetadataFromSDK(input.manager) + if (!manager) { + return undefined + } + return { + manager, + grant: grantFromSDK(input.grant) + } +} + +export const userManagerListFromSDK = (input?: full.UserManager[]) => + input ? input.map((d) => userManagerFromSDK(d)).filter(removeNullable) : [] From 4dd1649f7cfbd9facbe04e3050a18d1eeadb44fa Mon Sep 17 00:00:00 2001 From: Randy Schott <1815175+schottra@users.noreply.github.com> Date: Mon, 29 Apr 2024 16:42:58 -0400 Subject: [PATCH 06/14] add schema for manager users --- packages/common/src/api/account.ts | 2 +- packages/common/src/audius-query/schema.ts | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/common/src/api/account.ts b/packages/common/src/api/account.ts index 817b177997a..8e44aabbaa7 100644 --- a/packages/common/src/api/account.ts +++ b/packages/common/src/api/account.ts @@ -65,7 +65,7 @@ const accountApi = createApi({ }, options: { type: 'query', - schemaKey: 'managedUsers' + schemaKey: 'userManagers' } } } diff --git a/packages/common/src/audius-query/schema.ts b/packages/common/src/audius-query/schema.ts index b855749377f..f2015b52e1d 100644 --- a/packages/common/src/audius-query/schema.ts +++ b/packages/common/src/audius-query/schema.ts @@ -26,9 +26,14 @@ export const managedUserSchema = new schema.Object({ user: userSchema }) +export const userManagerSchema = new schema.Object({ + manager: userSchema +}) + export const apiResponseSchema = new schema.Object({ managedUsers: new schema.Array(managedUserSchema), user: userSchema, + userManagers: new schema.Array(userManagerSchema), track: trackSchema, collection: collectionSchema, users: new schema.Array(userSchema), From 7173c7611b41001157a003fbe6447e92ed9582c3 Mon Sep 17 00:00:00 2001 From: Randy Schott <1815175+schottra@users.noreply.github.com> Date: Mon, 29 Apr 2024 16:49:01 -0400 Subject: [PATCH 07/14] lint fix --- packages/common/src/models/Grant.ts | 4 ++-- packages/web/src/components/nav/desktop/AccountDetails.tsx | 4 ++-- .../nav/desktop/AccountSwitcher/AccountSwitcher.tsx | 4 ---- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/common/src/models/Grant.ts b/packages/common/src/models/Grant.ts index fbcea36b91a..a6ded695c0c 100644 --- a/packages/common/src/models/Grant.ts +++ b/packages/common/src/models/Grant.ts @@ -1,4 +1,4 @@ -import { Grant as SDKGrant } from '@audius/sdk' +import { full } from '@audius/sdk' import { Nullable, decodeHashId } from '~/utils' import { ID } from './Identifiers' @@ -11,7 +11,7 @@ export type Grant = { updated_at: string } -export const grantFromSDK = (input: SDKGrant): Grant => { +export const grantFromSDK = (input: full.Grant): Grant => { return { grantee_address: input.granteeAddress, user_id: decodeHashId(input.userId) ?? null, diff --git a/packages/web/src/components/nav/desktop/AccountDetails.tsx b/packages/web/src/components/nav/desktop/AccountDetails.tsx index 1974b11a148..8acf880c051 100644 --- a/packages/web/src/components/nav/desktop/AccountDetails.tsx +++ b/packages/web/src/components/nav/desktop/AccountDetails.tsx @@ -1,14 +1,14 @@ +import { FeatureFlags } from '@audius/common/services' import { accountSelectors } from '@audius/common/store' import { Text } from '@audius/harmony' import { AvatarLegacy } from 'components/avatar/AvatarLegacy' import { TextLink, UserLink } from 'components/link' +import { useFlag } from 'hooks/useRemoteConfig' import { useSelector } from 'utils/reducer' import { SIGN_IN_PAGE, profilePage } from 'utils/route' import styles from './AccountDetails.module.css' -import { FeatureFlags } from '@audius/common/services' -import { useFlag } from 'hooks/useRemoteConfig' import { AccountSwitcher } from './AccountSwitcher/AccountSwitcher' const { getAccountUser } = accountSelectors diff --git a/packages/web/src/components/nav/desktop/AccountSwitcher/AccountSwitcher.tsx b/packages/web/src/components/nav/desktop/AccountSwitcher/AccountSwitcher.tsx index 775b835fd21..1580638be28 100644 --- a/packages/web/src/components/nav/desktop/AccountSwitcher/AccountSwitcher.tsx +++ b/packages/web/src/components/nav/desktop/AccountSwitcher/AccountSwitcher.tsx @@ -1,14 +1,10 @@ import { useGetManagedAccounts } from '@audius/common/api' -import { useAudiusQueryContext } from '@audius/common/audius-query' import { IconButton, IconCaretDown } from '@audius/harmony' -import { useEffect, useState } from 'react' export const AccountSwitcher = () => { const { data: managedAccounts } = useGetManagedAccounts({}) const onClickExpander = () => {} - console.log(managedAccounts) - return managedAccounts && managedAccounts.length ? ( Date: Thu, 2 May 2024 12:53:01 -0400 Subject: [PATCH 08/14] Finish styling and behavior for basic account switcher --- .../AccountSwitcher/AccountListContent.tsx | 83 +++++++++++++++++ .../AccountSwitcher/AccountSwitcher.tsx | 70 ++++++++++++--- .../AccountSwitcherRow.module.css | 27 ++++++ .../AccountSwitcher/AccountSwitcherRow.tsx | 88 +++++++++++++++++++ 4 files changed, 256 insertions(+), 12 deletions(-) create mode 100644 packages/web/src/components/nav/desktop/AccountSwitcher/AccountListContent.tsx create mode 100644 packages/web/src/components/nav/desktop/AccountSwitcher/AccountSwitcherRow.module.css create mode 100644 packages/web/src/components/nav/desktop/AccountSwitcher/AccountSwitcherRow.tsx diff --git a/packages/web/src/components/nav/desktop/AccountSwitcher/AccountListContent.tsx b/packages/web/src/components/nav/desktop/AccountSwitcher/AccountListContent.tsx new file mode 100644 index 00000000000..9a80ca05540 --- /dev/null +++ b/packages/web/src/components/nav/desktop/AccountSwitcher/AccountListContent.tsx @@ -0,0 +1,83 @@ +import { ManagedUserMetadata, User } from '@audius/common/models' +import { Box, Flex, IconUserGroup, Text, useTheme } from '@audius/harmony' +import styled from '@emotion/styled' +import { AccountSwitcherRow } from './AccountSwitcherRow' + +const messages = { + switchAccount: 'Switch Account', + maangedAccounts: 'Managed Accounts' +} + +export type AccountListContentProps = { + accounts: ManagedUserMetadata[] + managerAccount: User +} + +const StyledList = styled.ul` + all: unset; + list-style: none; +` + +export const AccountListContent = ({ + accounts, + managerAccount +}: AccountListContentProps) => { + const theme = useTheme() + // TODO: aria-labels + return ( + + + + + {messages.switchAccount} + + + +
  • + +
  • + + + {messages.maangedAccounts} + + + + {accounts.map(({ user }, i) => ( +
  • console.log(`clicked ${user.user_id}`)} + tabIndex={-1} + > + + + +
  • + ))} +
    +
    + ) +} + +// Make ticket for migrating follows you thing +// Talk about in meeting about how to hide high level pages diff --git a/packages/web/src/components/nav/desktop/AccountSwitcher/AccountSwitcher.tsx b/packages/web/src/components/nav/desktop/AccountSwitcher/AccountSwitcher.tsx index 1580638be28..250d941d9f4 100644 --- a/packages/web/src/components/nav/desktop/AccountSwitcher/AccountSwitcher.tsx +++ b/packages/web/src/components/nav/desktop/AccountSwitcher/AccountSwitcher.tsx @@ -1,17 +1,63 @@ +import { useCallback, useMemo, useRef, useState } from 'react' + import { useGetManagedAccounts } from '@audius/common/api' -import { IconButton, IconCaretDown } from '@audius/harmony' +import { Flex, IconButton, IconCaretDown, Popup } from '@audius/harmony' + +import { AccountListContent } from './AccountListContent' +import { useSelector } from 'react-redux' +import { accountSelectors } from '@audius/common/store' export const AccountSwitcher = () => { - const { data: managedAccounts } = useGetManagedAccounts({}) - const onClickExpander = () => {} + const { data: managedAccounts = [] } = useGetManagedAccounts({}) + const [isExpanded, setIsExpanded] = useState(false) + + const managerAccount = useSelector(accountSelectors.getAccountUser) + + const parentElementRef = useRef(null) + const onClickExpander = useCallback( + () => setIsExpanded((expanded) => !expanded), + [] + ) + + const accounts = useMemo(() => { + return managedAccounts.filter(({ grant }) => grant.is_approved) + }, [managedAccounts]) - return managedAccounts && managedAccounts.length ? ( - - ) : null + return managerAccount === null || accounts.length === 0 ? null : ( + + + { + if (target instanceof Element && parentElementRef.current) { + return parentElementRef.current.contains(target) + } + return false + }} + anchorRef={parentElementRef} + anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }} + transformOrigin={{ vertical: 'top', horizontal: 'left' }} + dismissOnMouseLeave={false} + isVisible={isExpanded} + onClose={() => setIsExpanded(false)} + > + + + + ) } diff --git a/packages/web/src/components/nav/desktop/AccountSwitcher/AccountSwitcherRow.module.css b/packages/web/src/components/nav/desktop/AccountSwitcher/AccountSwitcherRow.module.css new file mode 100644 index 00000000000..e80cfb45737 --- /dev/null +++ b/packages/web/src/components/nav/desktop/AccountSwitcher/AccountSwitcherRow.module.css @@ -0,0 +1,27 @@ +:root { + --profile-picture-size: var(--harmony-unit-12); +} + +.profilePictureWrapper { + height: var(--profile-picture-size); + width: var(--profile-picture-size); + flex: 0 0 var(--profile-picture-size); +} + +.profilePicture { + box-sizing: border-box; + height: var(--profile-picture-size); + width: var(--profile-picture-size); + flex: 0 0 var(--profile-picture-size); + border: 1px solid var(--harmony-n-100); + border-radius: 50%; + + background-size: cover; + background-position: center; + background-repeat: no-repeat; + background-color: var(--default-profile-picture-background); +} + +.profilePictureSkeleton { + border-radius: 50%; +} diff --git a/packages/web/src/components/nav/desktop/AccountSwitcher/AccountSwitcherRow.tsx b/packages/web/src/components/nav/desktop/AccountSwitcher/AccountSwitcherRow.tsx new file mode 100644 index 00000000000..1ae47dca52d --- /dev/null +++ b/packages/web/src/components/nav/desktop/AccountSwitcher/AccountSwitcherRow.tsx @@ -0,0 +1,88 @@ +import { SquareSizes, UserMetadata } from '@audius/common/models' +import { Flex, Text, useTheme } from '@audius/harmony' +import DynamicImage from 'components/dynamic-image/DynamicImage' +import UserBadges from 'components/user-badges/UserBadges' +import { useProfilePicture } from 'hooks/useUserProfilePicture' + +import styled from '@emotion/styled' +import styles from './AccountSwitcherRow.module.css' + +export type AccountSwitcherRowProps = { + user: UserMetadata + isSelected?: boolean +} + +const Indicator = styled.div(({ theme }) => ({ + position: 'absolute', + top: 0, + left: 0, + bottom: 0, + width: theme.spacing.xs, + backgroundColor: theme.color.background.accent +})) + +export const AccountSwitcherRow = ({ + user, + isSelected = false +}: AccountSwitcherRowProps) => { + const profilePicture = useProfilePicture( + user.user_id, + SquareSizes.SIZE_150_BY_150 + ) + const { iconSizes, color } = useTheme() + return ( + + {isSelected && } + + + + + {user.name} + + + + {`@${user.handle}`} + + + ) +} From 63544f7ff0b9ea312b7c3efd983eeff68c93168f Mon Sep 17 00:00:00 2001 From: Randy Schott <1815175+schottra@users.noreply.github.com> Date: Fri, 3 May 2024 16:32:20 -0400 Subject: [PATCH 09/14] wire up saving user override and refreshing --- packages/common/src/api/account.ts | 42 ++++++++---- packages/common/src/audius-query/schema.ts | 1 + .../services/audius-backend/AudiusBackend.ts | 2 +- packages/libs/src/api/Account.ts | 10 +++ .../discoveryProvider/DiscoveryProvider.ts | 64 +++++++++++-------- .../services/discoveryProvider/constants.ts | 2 + packages/libs/src/userStateManager.ts | 14 ++++ .../AccountSwitcher/AccountListContent.tsx | 47 +++++++++++--- .../AccountSwitcher/AccountSwitcher.tsx | 40 ++++++++++-- 9 files changed, 166 insertions(+), 56 deletions(-) diff --git a/packages/common/src/api/account.ts b/packages/common/src/api/account.ts index 8e44aabbaa7..a51cae8a3d5 100644 --- a/packages/common/src/api/account.ts +++ b/packages/common/src/api/account.ts @@ -1,6 +1,11 @@ import { createApi } from '~/audius-query' import { Id } from './utils' -import { managedUserListFromSDK, userManagerListFromSDK } from '~/models' +import { + ID, + UserMetadata, + managedUserListFromSDK, + userManagerListFromSDK +} from '~/models' type ResetPasswordArgs = { email: string @@ -20,6 +25,19 @@ const accountApi = createApi({ type: 'query' } }, + getCurrentWeb3User: { + async fetch(_, { audiusBackend }) { + const libs = await audiusBackend.getAudiusLibsTyped() + // TODO: What happens in the cache if something here is null? + // Note: This cast is mostly safe, but is missing info populated in AudiusBackend.getAccount() + // Okay for now as that info isn't generally available on + return libs.Account?.getWeb3User() as UserMetadata | null + }, + options: { + type: 'query', + schemaKey: 'currentWeb3User' + } + }, resetPassword: { async fetch(args: ResetPasswordArgs, context) { const { email, password } = args @@ -33,13 +51,10 @@ const accountApi = createApi({ } }, getManagedAccounts: { - async fetch(_, { audiusSdk, audiusBackend }) { + async fetch({ userId }: { userId: ID }, { audiusSdk }) { const sdk = await audiusSdk() - // TODO: Look this up in a better way - // https://linear.app/audius/issue/PAY-2816/look-up-parentchild-ids-from-a-better-place - const currentUserId = (await audiusBackend.getAccount())?.user_id const managedUsers = await sdk.full.users.getManagedUsers({ - id: Id.parse(currentUserId) + id: Id.parse(userId) }) const { data = [] } = managedUsers @@ -51,13 +66,10 @@ const accountApi = createApi({ } }, getManagers: { - async fetch(_, { audiusSdk, audiusBackend }) { + async fetch({ userId }: { userId: ID }, { audiusSdk }) { const sdk = await audiusSdk() - // TODO: Look this up in a better way - // https://linear.app/audius/issue/PAY-2816/look-up-parentchild-ids-from-a-better-place - const currentUserId = (await audiusBackend.getAccount())?.user_id const managedUsers = await sdk.full.users.getManagers({ - id: Id.parse(currentUserId) + id: Id.parse(userId) }) const { data = [] } = managedUsers @@ -71,7 +83,11 @@ const accountApi = createApi({ } }) -export const { useGetCurrentUserId, useResetPassword, useGetManagedAccounts } = - accountApi.hooks +export const { + useGetCurrentUserId, + useGetCurrentWeb3User, + useResetPassword, + useGetManagedAccounts +} = accountApi.hooks export const accountApiReducer = accountApi.reducer diff --git a/packages/common/src/audius-query/schema.ts b/packages/common/src/audius-query/schema.ts index f2015b52e1d..09c71942185 100644 --- a/packages/common/src/audius-query/schema.ts +++ b/packages/common/src/audius-query/schema.ts @@ -31,6 +31,7 @@ export const userManagerSchema = new schema.Object({ }) export const apiResponseSchema = new schema.Object({ + currentWeb3User: userSchema, managedUsers: new schema.Array(managedUserSchema), user: userSchema, userManagers: new schema.Array(userManagerSchema), diff --git a/packages/common/src/services/audius-backend/AudiusBackend.ts b/packages/common/src/services/audius-backend/AudiusBackend.ts index c29f786f9f3..4bc5752afd9 100644 --- a/packages/common/src/services/audius-backend/AudiusBackend.ts +++ b/packages/common/src/services/audius-backend/AudiusBackend.ts @@ -706,7 +706,7 @@ export const audiusBackend = ({ getRemoteVar(IntKeys.DISCOVERY_NODE_MAX_BLOCK_DIFF) ?? undefined, discoveryNodeSelector, - enableUserIdOverride: isManagerModeEnabled + enableUserWalletOverride: isManagerModeEnabled }, identityServiceConfig: AudiusLibs.configIdentityService(identityServiceUrl), diff --git a/packages/libs/src/api/Account.ts b/packages/libs/src/api/Account.ts index d82175944e1..c6ea9d6f4f5 100644 --- a/packages/libs/src/api/Account.ts +++ b/packages/libs/src/api/Account.ts @@ -25,6 +25,7 @@ export class Account extends Base { this.ServiceProvider = serviceProvider this.getCurrentUser = this.getCurrentUser.bind(this) + this.getWeb3User = this.getWeb3User.bind(this) this.login = this.login.bind(this) this.logout = this.logout.bind(this) this.generateRecoveryLink = this.generateRecoveryLink.bind(this) @@ -54,6 +55,15 @@ export class Account extends Base { return this.userStateManager.getCurrentUser() } + /** + * Fetches the user metadata for user belonging to the current web3 wallet. + * May be different if acting as a managed account. + * @return {Object} user metadata + */ + getWeb3User() { + return this.userStateManager.getWeb3User() + } + /** * Logs a user into Audius */ diff --git a/packages/libs/src/services/discoveryProvider/DiscoveryProvider.ts b/packages/libs/src/services/discoveryProvider/DiscoveryProvider.ts index 4842f56f76a..60dcd4ff7ba 100644 --- a/packages/libs/src/services/discoveryProvider/DiscoveryProvider.ts +++ b/packages/libs/src/services/discoveryProvider/DiscoveryProvider.ts @@ -21,7 +21,11 @@ import { DiscoveryProviderSelection, DiscoveryProviderSelectionConfig } from './DiscoveryProviderSelection' -import { DEFAULT_UNHEALTHY_BLOCK_DIFF, REQUEST_TIMEOUT_MS } from './constants' +import { + DEFAULT_UNHEALTHY_BLOCK_DIFF, + DISCOVERY_PROVIDER_USER_WALLET_OVERRIDE, + REQUEST_TIMEOUT_MS +} from './constants' import * as Requests from './requests' const MAX_MAKE_REQUEST_RETRY_COUNT = 5 @@ -65,7 +69,7 @@ export type DiscoveryProviderConfig = { unhealthySlotDiffPlays?: number unhealthyBlockDiff?: number discoveryNodeSelector?: DiscoveryNodeSelector - enableUserIdOverride?: boolean + enableUserWalletOverride?: boolean } & Pick< DiscoveryProviderSelectionConfig, 'selectionCallback' | 'monitoringCallbacks' | 'localStorage' @@ -97,10 +101,10 @@ type DiscoveryNodeChallenge = { disbursed_amount: number } -const getUserIdOverride = async (localStorage?: LocalStorage) => { +const getUserWalletOverride = async (localStorage?: LocalStorage) => { try { const userIdOverride = await localStorage?.getItem( - '@audius/user-id-override' + DISCOVERY_PROVIDER_USER_WALLET_OVERRIDE ) return userIdOverride == null ? undefined : userIdOverride } catch { @@ -152,7 +156,7 @@ export class DiscoveryProvider { | DiscoveryProviderSelection['monitoringCallbacks'] | undefined - enableUserIdOverride = false + enableUserWalletOverride = false discoveryProviderEndpoint: string | undefined isInitialized = false @@ -176,7 +180,7 @@ export class DiscoveryProvider { unhealthySlotDiffPlays, unhealthyBlockDiff, discoveryNodeSelector, - enableUserIdOverride = false + enableUserWalletOverride = false }: DiscoveryProviderConfig) { this.whitelist = whitelist this.blacklist = blacklist @@ -185,7 +189,7 @@ export class DiscoveryProvider { this.web3Manager = web3Manager this.selectionCallback = selectionCallback this.localStorage = localStorage - this.enableUserIdOverride = enableUserIdOverride + this.enableUserWalletOverride = enableUserWalletOverride this.unhealthyBlockDiff = unhealthyBlockDiff ?? DEFAULT_UNHEALTHY_BLOCK_DIFF this.serviceSelector = new DiscoveryProviderSelection( @@ -243,12 +247,33 @@ export class DiscoveryProvider { this.web3Manager && this.web3Manager.web3 ) { - // Set current user if it exists - const userAccount = await this.getUserAccount( + const walletOverride = this.enableUserWalletOverride + ? await getUserWalletOverride(this.localStorage) + : undefined + + const web3AccountPromise = this.getUserAccount( this.web3Manager.getWalletAddress() ) - if (userAccount) { - await this.userStateManager.setCurrentUser(userAccount) + + if (walletOverride) { + const overrideAccountPromise = this.getUserAccount(walletOverride) + const [web3User, currentUser] = await Promise.all([ + web3AccountPromise, + overrideAccountPromise + ]) + + if (web3User) { + this.userStateManager.setWeb3User(web3User) + } + if (currentUser) { + await this.userStateManager.setCurrentUser(currentUser) + } + } else { + const currentUser = await web3AccountPromise + if (currentUser) { + this.userStateManager.setWeb3User(currentUser) + await this.userStateManager.setCurrentUser(currentUser) + } } } } @@ -839,21 +864,8 @@ export class DiscoveryProvider { * Return user collections (saved & uploaded) along w/ users for those collections */ async getUserAccount(wallet: string) { - const userIdOverride = this.enableUserIdOverride - ? await getUserIdOverride(this.localStorage) - : undefined - // If override is used, fetch that account instead - if (userIdOverride) { - const req = Requests.getUsers(1, 0, [parseInt(userIdOverride)]) - const res = await this._makeRequest(req) - if (res && res.length > 0 && res[0]) { - return { ...res[0], playlists: [] } as CurrentUser - } - return null - } else { - const req = Requests.getUserAccount(wallet) - return await this._makeRequest(req) - } + const req = Requests.getUserAccount(wallet) + return await this._makeRequest(req) } /** diff --git a/packages/libs/src/services/discoveryProvider/constants.ts b/packages/libs/src/services/discoveryProvider/constants.ts index f95eaad67ac..fa81fc1e64d 100644 --- a/packages/libs/src/services/discoveryProvider/constants.ts +++ b/packages/libs/src/services/discoveryProvider/constants.ts @@ -1,5 +1,7 @@ export const DISCOVERY_PROVIDER_TIMESTAMP = '@audius/libs:discovery-node-timestamp' +export const DISCOVERY_PROVIDER_USER_WALLET_OVERRIDE = + '@audius/user-wallet-override' export const DISCOVERY_SERVICE_NAME = 'discovery-node' export const DEFAULT_UNHEALTHY_BLOCK_DIFF = 15 export const REGRESSED_MODE_TIMEOUT = 2 * 60 * 1000 // two minutes diff --git a/packages/libs/src/userStateManager.ts b/packages/libs/src/userStateManager.ts index 4ff581ecac7..8acb9418aec 100644 --- a/packages/libs/src/userStateManager.ts +++ b/packages/libs/src/userStateManager.ts @@ -19,11 +19,13 @@ type UserStateManagerConfig = { */ export class UserStateManager { currentUser: CurrentUser | null + web3User: CurrentUser | null localStorage?: LocalStorage constructor({ localStorage }: UserStateManagerConfig) { // Should reflect the same fields as discovery node's /users?handle= this.currentUser = null + this.web3User = null this.localStorage = localStorage } @@ -41,14 +43,26 @@ export class UserStateManager { } } + setWeb3User(user: CurrentUser) { + this.web3User = user + } + getCurrentUser() { return this.currentUser } + getWeb3User() { + return this.web3User + } + getCurrentUserId() { return this.currentUser ? this.currentUser.user_id : null } + getWeb3UserId() { + return this.web3User ? this.web3User.user_id : null + } + async clearUser() { this.currentUser = null if (this.localStorage) { diff --git a/packages/web/src/components/nav/desktop/AccountSwitcher/AccountListContent.tsx b/packages/web/src/components/nav/desktop/AccountSwitcher/AccountListContent.tsx index 9a80ca05540..03e83cddb1e 100644 --- a/packages/web/src/components/nav/desktop/AccountSwitcher/AccountListContent.tsx +++ b/packages/web/src/components/nav/desktop/AccountSwitcher/AccountListContent.tsx @@ -1,7 +1,13 @@ -import { ManagedUserMetadata, User } from '@audius/common/models' -import { Box, Flex, IconUserGroup, Text, useTheme } from '@audius/harmony' +import { + ID, + ManagedUserMetadata, + User, + UserMetadata +} from '@audius/common/models' +import { Box, Flex, IconUserArrowRotate, Text, useTheme } from '@audius/harmony' import styled from '@emotion/styled' import { AccountSwitcherRow } from './AccountSwitcherRow' +import { useCallback } from 'react' const messages = { switchAccount: 'Switch Account', @@ -10,7 +16,9 @@ const messages = { export type AccountListContentProps = { accounts: ManagedUserMetadata[] - managerAccount: User + managerAccount: UserMetadata + currentUserId: ID + onAccountSelected: (user: UserMetadata) => void } const StyledList = styled.ul` @@ -20,9 +28,20 @@ const StyledList = styled.ul` export const AccountListContent = ({ accounts, - managerAccount + managerAccount, + currentUserId, + onAccountSelected }: AccountListContentProps) => { const theme = useTheme() + + const onUserSelected = useCallback( + (user: UserMetadata) => { + if (user.user_id !== currentUserId) { + onAccountSelected(user) + } + }, + [currentUserId, onAccountSelected] + ) // TODO: aria-labels return ( - + {messages.switchAccount} -
  • - +
  • onAccountSelected(managerAccount)} + > +
  • console.log(`clicked ${user.user_id}`)} + onClick={() => onAccountSelected(user)} tabIndex={-1} > - + ))} diff --git a/packages/web/src/components/nav/desktop/AccountSwitcher/AccountSwitcher.tsx b/packages/web/src/components/nav/desktop/AccountSwitcher/AccountSwitcher.tsx index 250d941d9f4..29b2316a646 100644 --- a/packages/web/src/components/nav/desktop/AccountSwitcher/AccountSwitcher.tsx +++ b/packages/web/src/components/nav/desktop/AccountSwitcher/AccountSwitcher.tsx @@ -1,17 +1,41 @@ import { useCallback, useMemo, useRef, useState } from 'react' -import { useGetManagedAccounts } from '@audius/common/api' +import { + useGetCurrentUserId, + useGetCurrentWeb3User, + useGetManagedAccounts +} from '@audius/common/api' import { Flex, IconButton, IconCaretDown, Popup } from '@audius/harmony' +import { localStorage } from 'services/local-storage' import { AccountListContent } from './AccountListContent' -import { useSelector } from 'react-redux' -import { accountSelectors } from '@audius/common/store' +import { UserMetadata } from '@audius/common/models' + +// Matches corresponding key in libs (DISCOVERY_PROVIDER_USER_WALLET_OVERRIDE) +const USER_WALLET_OVERRIDE_KEY = '@audius/user-wallet-override' export const AccountSwitcher = () => { - const { data: managedAccounts = [] } = useGetManagedAccounts({}) const [isExpanded, setIsExpanded] = useState(false) - const managerAccount = useSelector(accountSelectors.getAccountUser) + const { data: currentWeb3User } = useGetCurrentWeb3User({}) + const { data: currentUserId } = useGetCurrentUserId({}) + + const onAccountSelected = useCallback((user: UserMetadata) => { + if (!user.wallet) { + console.error('User has no wallet address') + return + } + localStorage.setItem(USER_WALLET_OVERRIDE_KEY, user.wallet).then(() => { + // TODO: Don't use window directly? + // TODO: Need to clear the user from localStorage before switching or we get a flash of previous user. + window.location.reload() + }) + }, []) + + const { data: managedAccounts = [] } = useGetManagedAccounts( + { userId: currentWeb3User?.user_id! }, + { disabled: !currentWeb3User } + ) const parentElementRef = useRef(null) const onClickExpander = useCallback( @@ -23,7 +47,7 @@ export const AccountSwitcher = () => { return managedAccounts.filter(({ grant }) => grant.is_approved) }, [managedAccounts]) - return managerAccount === null || accounts.length === 0 ? null : ( + return !currentWeb3User || !currentUserId || accounts.length === 0 ? null : ( { onClose={() => setIsExpanded(false)} > From adb612927107f0011f697de6461d294170082c64 Mon Sep 17 00:00:00 2001 From: Randy Schott <1815175+schottra@users.noreply.github.com> Date: Mon, 6 May 2024 11:19:12 -0400 Subject: [PATCH 10/14] fix stale account showing on switch --- packages/common/src/hooks/index.ts | 1 + .../common/src/hooks/useAccountSwitcher.ts | 25 +++++++++++++++++++ .../AccountSwitcher/AccountSwitcher.tsx | 17 +++---------- 3 files changed, 30 insertions(+), 13 deletions(-) create mode 100644 packages/common/src/hooks/useAccountSwitcher.ts diff --git a/packages/common/src/hooks/index.ts b/packages/common/src/hooks/index.ts index 845523bdc56..f76de3cb310 100644 --- a/packages/common/src/hooks/index.ts +++ b/packages/common/src/hooks/index.ts @@ -1,3 +1,4 @@ +export * from './useAccountSwitcher' export * from './useBooleanOnce' export * from './useFeatureFlag' export * from './useRemoteVar' diff --git a/packages/common/src/hooks/useAccountSwitcher.ts b/packages/common/src/hooks/useAccountSwitcher.ts new file mode 100644 index 00000000000..a070eb8041b --- /dev/null +++ b/packages/common/src/hooks/useAccountSwitcher.ts @@ -0,0 +1,25 @@ +import { useCallback } from 'react' +import { useAppContext } from '~/context' +import { UserMetadata } from '~/models/User' + +// Matches corresponding key in libs (DISCOVERY_PROVIDER_USER_WALLET_OVERRIDE) +const USER_WALLET_OVERRIDE_KEY = '@audius/user-wallet-override' + +export const useAccountSwitcher = () => { + const { localStorage } = useAppContext() + const switchAccount = useCallback(async (user: UserMetadata) => { + if (!user.wallet) { + console.error('User has no wallet address') + return + } + + await localStorage.setItem(USER_WALLET_OVERRIDE_KEY, user.wallet) + await localStorage.clearAudiusAccount() + await localStorage.clearAudiusAccountUser() + + // TODO: Need to clear the user from localStorage before switching or we get a flash of previous user. + window.location.reload() + }, []) + + return { switchAccount } +} diff --git a/packages/web/src/components/nav/desktop/AccountSwitcher/AccountSwitcher.tsx b/packages/web/src/components/nav/desktop/AccountSwitcher/AccountSwitcher.tsx index 29b2316a646..070732b3d85 100644 --- a/packages/web/src/components/nav/desktop/AccountSwitcher/AccountSwitcher.tsx +++ b/packages/web/src/components/nav/desktop/AccountSwitcher/AccountSwitcher.tsx @@ -6,13 +6,10 @@ import { useGetManagedAccounts } from '@audius/common/api' import { Flex, IconButton, IconCaretDown, Popup } from '@audius/harmony' -import { localStorage } from 'services/local-storage' import { AccountListContent } from './AccountListContent' import { UserMetadata } from '@audius/common/models' - -// Matches corresponding key in libs (DISCOVERY_PROVIDER_USER_WALLET_OVERRIDE) -const USER_WALLET_OVERRIDE_KEY = '@audius/user-wallet-override' +import { useAccountSwitcher } from '@audius/common/hooks' export const AccountSwitcher = () => { const [isExpanded, setIsExpanded] = useState(false) @@ -20,16 +17,10 @@ export const AccountSwitcher = () => { const { data: currentWeb3User } = useGetCurrentWeb3User({}) const { data: currentUserId } = useGetCurrentUserId({}) + const { switchAccount } = useAccountSwitcher() + const onAccountSelected = useCallback((user: UserMetadata) => { - if (!user.wallet) { - console.error('User has no wallet address') - return - } - localStorage.setItem(USER_WALLET_OVERRIDE_KEY, user.wallet).then(() => { - // TODO: Don't use window directly? - // TODO: Need to clear the user from localStorage before switching or we get a flash of previous user. - window.location.reload() - }) + switchAccount(user) }, []) const { data: managedAccounts = [] } = useGetManagedAccounts( From 5dd0c216baddd970530f4628455d9cd820489c8b Mon Sep 17 00:00:00 2001 From: Randy Schott <1815175+schottra@users.noreply.github.com> Date: Mon, 6 May 2024 11:26:27 -0400 Subject: [PATCH 11/14] lint fixes --- .../AccountSwitcher/AccountListContent.tsx | 20 +++++++------------ .../AccountSwitcher/AccountSwitcher.tsx | 19 +++++++++++------- .../AccountSwitcher/AccountSwitcherRow.tsx | 3 ++- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/packages/web/src/components/nav/desktop/AccountSwitcher/AccountListContent.tsx b/packages/web/src/components/nav/desktop/AccountSwitcher/AccountListContent.tsx index 03e83cddb1e..050f4d89652 100644 --- a/packages/web/src/components/nav/desktop/AccountSwitcher/AccountListContent.tsx +++ b/packages/web/src/components/nav/desktop/AccountSwitcher/AccountListContent.tsx @@ -1,13 +1,10 @@ -import { - ID, - ManagedUserMetadata, - User, - UserMetadata -} from '@audius/common/models' -import { Box, Flex, IconUserArrowRotate, Text, useTheme } from '@audius/harmony' +import { useCallback } from 'react' + +import { ID, ManagedUserMetadata, UserMetadata } from '@audius/common/models' +import { Box, Flex, IconUserArrowRotate, Text } from '@audius/harmony' import styled from '@emotion/styled' + import { AccountSwitcherRow } from './AccountSwitcherRow' -import { useCallback } from 'react' const messages = { switchAccount: 'Switch Account', @@ -32,8 +29,6 @@ export const AccountListContent = ({ currentUserId, onAccountSelected }: AccountListContentProps) => { - const theme = useTheme() - const onUserSelected = useCallback( (user: UserMetadata) => { if (user.user_id !== currentUserId) { @@ -42,7 +37,6 @@ export const AccountListContent = ({ }, [currentUserId, onAccountSelected] ) - // TODO: aria-labels return ( onAccountSelected(managerAccount)} + onClick={() => onUserSelected(managerAccount)} > onAccountSelected(user)} + onClick={() => onUserSelected(user)} tabIndex={-1} > diff --git a/packages/web/src/components/nav/desktop/AccountSwitcher/AccountSwitcher.tsx b/packages/web/src/components/nav/desktop/AccountSwitcher/AccountSwitcher.tsx index 070732b3d85..e3cd4d4b494 100644 --- a/packages/web/src/components/nav/desktop/AccountSwitcher/AccountSwitcher.tsx +++ b/packages/web/src/components/nav/desktop/AccountSwitcher/AccountSwitcher.tsx @@ -5,11 +5,11 @@ import { useGetCurrentWeb3User, useGetManagedAccounts } from '@audius/common/api' +import { useAccountSwitcher } from '@audius/common/hooks' +import { UserMetadata } from '@audius/common/models' import { Flex, IconButton, IconCaretDown, Popup } from '@audius/harmony' import { AccountListContent } from './AccountListContent' -import { UserMetadata } from '@audius/common/models' -import { useAccountSwitcher } from '@audius/common/hooks' export const AccountSwitcher = () => { const [isExpanded, setIsExpanded] = useState(false) @@ -19,13 +19,18 @@ export const AccountSwitcher = () => { const { switchAccount } = useAccountSwitcher() - const onAccountSelected = useCallback((user: UserMetadata) => { - switchAccount(user) - }, []) + const onAccountSelected = useCallback( + (user: UserMetadata) => { + switchAccount(user) + }, + [switchAccount] + ) + + const web3UserId = currentWeb3User?.user_id ?? null const { data: managedAccounts = [] } = useGetManagedAccounts( - { userId: currentWeb3User?.user_id! }, - { disabled: !currentWeb3User } + { userId: web3UserId! }, + { disabled: !web3UserId } ) const parentElementRef = useRef(null) diff --git a/packages/web/src/components/nav/desktop/AccountSwitcher/AccountSwitcherRow.tsx b/packages/web/src/components/nav/desktop/AccountSwitcher/AccountSwitcherRow.tsx index 1ae47dca52d..9226fff4750 100644 --- a/packages/web/src/components/nav/desktop/AccountSwitcher/AccountSwitcherRow.tsx +++ b/packages/web/src/components/nav/desktop/AccountSwitcher/AccountSwitcherRow.tsx @@ -1,10 +1,11 @@ import { SquareSizes, UserMetadata } from '@audius/common/models' import { Flex, Text, useTheme } from '@audius/harmony' +import styled from '@emotion/styled' + import DynamicImage from 'components/dynamic-image/DynamicImage' import UserBadges from 'components/user-badges/UserBadges' import { useProfilePicture } from 'hooks/useUserProfilePicture' -import styled from '@emotion/styled' import styles from './AccountSwitcherRow.module.css' export type AccountSwitcherRowProps = { From 670395fe2f9a2f96e4ab087ad02ba2e53c0e9b70 Mon Sep 17 00:00:00 2001 From: Randy Schott <1815175+schottra@users.noreply.github.com> Date: Mon, 6 May 2024 11:38:17 -0400 Subject: [PATCH 12/14] PR feedback --- packages/common/src/api/account.ts | 6 ++++-- packages/common/src/hooks/useAccountSwitcher.ts | 1 - .../nav/desktop/AccountSwitcher/AccountListContent.tsx | 3 --- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/common/src/api/account.ts b/packages/common/src/api/account.ts index a51cae8a3d5..e9f2da36beb 100644 --- a/packages/common/src/api/account.ts +++ b/packages/common/src/api/account.ts @@ -28,9 +28,11 @@ const accountApi = createApi({ getCurrentWeb3User: { async fetch(_, { audiusBackend }) { const libs = await audiusBackend.getAudiusLibsTyped() - // TODO: What happens in the cache if something here is null? + // TODO: https://linear.app/audius/issue/PAY-2838/separate-walletentropy-user-and-current-user-in-state + // What happens in the cache if something here is null? + // Note: This cast is mostly safe, but is missing info populated in AudiusBackend.getAccount() - // Okay for now as that info isn't generally available on + // Okay for now as that info isn't generally available on non-account users and isn't used in manager mode. return libs.Account?.getWeb3User() as UserMetadata | null }, options: { diff --git a/packages/common/src/hooks/useAccountSwitcher.ts b/packages/common/src/hooks/useAccountSwitcher.ts index a070eb8041b..cd3f5776a14 100644 --- a/packages/common/src/hooks/useAccountSwitcher.ts +++ b/packages/common/src/hooks/useAccountSwitcher.ts @@ -17,7 +17,6 @@ export const useAccountSwitcher = () => { await localStorage.clearAudiusAccount() await localStorage.clearAudiusAccountUser() - // TODO: Need to clear the user from localStorage before switching or we get a flash of previous user. window.location.reload() }, []) diff --git a/packages/web/src/components/nav/desktop/AccountSwitcher/AccountListContent.tsx b/packages/web/src/components/nav/desktop/AccountSwitcher/AccountListContent.tsx index 050f4d89652..607876c6a9d 100644 --- a/packages/web/src/components/nav/desktop/AccountSwitcher/AccountListContent.tsx +++ b/packages/web/src/components/nav/desktop/AccountSwitcher/AccountListContent.tsx @@ -101,6 +101,3 @@ export const AccountListContent = ({ ) } - -// Make ticket for migrating follows you thing -// Talk about in meeting about how to hide high level pages From 77e956c4dd118aa7f3bb041d7ad6e2d000ebe53c Mon Sep 17 00:00:00 2001 From: Randy Schott <1815175+schottra@users.noreply.github.com> Date: Mon, 6 May 2024 14:39:18 -0400 Subject: [PATCH 13/14] more lint fixes --- packages/common/src/api/account.ts | 3 ++- .../common/src/hooks/useAccountSwitcher.ts | 24 +++++++++++-------- packages/common/src/models/Grant.ts | 2 ++ packages/common/src/models/User.ts | 2 +- 4 files changed, 19 insertions(+), 12 deletions(-) diff --git a/packages/common/src/api/account.ts b/packages/common/src/api/account.ts index e9f2da36beb..3876712b337 100644 --- a/packages/common/src/api/account.ts +++ b/packages/common/src/api/account.ts @@ -1,5 +1,4 @@ import { createApi } from '~/audius-query' -import { Id } from './utils' import { ID, UserMetadata, @@ -7,6 +6,8 @@ import { userManagerListFromSDK } from '~/models' +import { Id } from './utils' + type ResetPasswordArgs = { email: string password: string diff --git a/packages/common/src/hooks/useAccountSwitcher.ts b/packages/common/src/hooks/useAccountSwitcher.ts index cd3f5776a14..b547e7fd63e 100644 --- a/packages/common/src/hooks/useAccountSwitcher.ts +++ b/packages/common/src/hooks/useAccountSwitcher.ts @@ -1,4 +1,5 @@ import { useCallback } from 'react' + import { useAppContext } from '~/context' import { UserMetadata } from '~/models/User' @@ -7,18 +8,21 @@ const USER_WALLET_OVERRIDE_KEY = '@audius/user-wallet-override' export const useAccountSwitcher = () => { const { localStorage } = useAppContext() - const switchAccount = useCallback(async (user: UserMetadata) => { - if (!user.wallet) { - console.error('User has no wallet address') - return - } + const switchAccount = useCallback( + async (user: UserMetadata) => { + if (!user.wallet) { + console.error('User has no wallet address') + return + } - await localStorage.setItem(USER_WALLET_OVERRIDE_KEY, user.wallet) - await localStorage.clearAudiusAccount() - await localStorage.clearAudiusAccountUser() + await localStorage.setItem(USER_WALLET_OVERRIDE_KEY, user.wallet) + await localStorage.clearAudiusAccount() + await localStorage.clearAudiusAccountUser() - window.location.reload() - }, []) + window.location.reload() + }, + [localStorage] + ) return { switchAccount } } diff --git a/packages/common/src/models/Grant.ts b/packages/common/src/models/Grant.ts index a6ded695c0c..655735a83fd 100644 --- a/packages/common/src/models/Grant.ts +++ b/packages/common/src/models/Grant.ts @@ -1,5 +1,7 @@ import { full } from '@audius/sdk' + import { Nullable, decodeHashId } from '~/utils' + import { ID } from './Identifiers' export type Grant = { diff --git a/packages/common/src/models/User.ts b/packages/common/src/models/User.ts index b0186f6616e..17d32d3a06e 100644 --- a/packages/common/src/models/User.ts +++ b/packages/common/src/models/User.ts @@ -21,9 +21,9 @@ import { SolanaWalletAddress, StringWei, WalletAddress } from '~/models/Wallet' import { decodeHashId } from '~/utils/hashIds' import { Nullable, removeNullable } from '~/utils/typeUtils' +import { Grant, grantFromSDK } from './Grant' import { Timestamped } from './Timestamped' import { UserEvent } from './UserEvent' -import { Grant, grantFromSDK } from './Grant' export type UserMetadata = { album_count: number From 20573e3cdb4eae837ad207e6a1111e793d00dd6b Mon Sep 17 00:00:00 2001 From: Randy Schott <1815175+schottra@users.noreply.github.com> Date: Tue, 7 May 2024 10:57:00 -0400 Subject: [PATCH 14/14] fix typo --- .../nav/desktop/AccountSwitcher/AccountListContent.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/web/src/components/nav/desktop/AccountSwitcher/AccountListContent.tsx b/packages/web/src/components/nav/desktop/AccountSwitcher/AccountListContent.tsx index 607876c6a9d..2a09745ce24 100644 --- a/packages/web/src/components/nav/desktop/AccountSwitcher/AccountListContent.tsx +++ b/packages/web/src/components/nav/desktop/AccountSwitcher/AccountListContent.tsx @@ -8,7 +8,7 @@ import { AccountSwitcherRow } from './AccountSwitcherRow' const messages = { switchAccount: 'Switch Account', - maangedAccounts: 'Managed Accounts' + managedAccounts: 'Managed Accounts' } export type AccountListContentProps = { @@ -78,7 +78,7 @@ export const AccountListContent = ({ borderBottom='default' > - {messages.maangedAccounts} + {messages.managedAccounts}