Skip to content

Commit

Permalink
fix: refresh encrypted contact list after add/delete (#10562)
Browse files Browse the repository at this point in the history
* fix: mf-4981

* chore: del console

* chore: fix url

* chore: code review

* fix: refresh data

* fix: mf-5095

* chore: typo
  • Loading branch information
beyond009 committed Aug 22, 2023
1 parent 099abbd commit cfb47ba
Show file tree
Hide file tree
Showing 7 changed files with 148 additions and 47 deletions.
41 changes: 27 additions & 14 deletions packages/mask/src/extension/popups/hook/useFriends.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Services from '../../../extension/service.js'
import { first } from 'lodash-es'
import { isProfileIdentifier } from '@masknet/shared'
import { useInfiniteQuery, useQuery } from '@tanstack/react-query'
import { useCallback } from 'react'

export type FriendsInformation = Friend & {
profiles: BindingProof[]
Expand All @@ -18,20 +19,28 @@ export type Friend = {

export function useFriendsPaged() {
const currentPersona = useCurrentPersona()
const { data: records = EMPTY_LIST, isLoading: recordsLoading } = useQuery(
['relation-records', currentPersona?.identifier.rawPublicKey],
async () => {
return Services.Identity.queryRelationPaged(
currentPersona?.identifier,
{
network: 'all',
pageOffset: 0,
},
3000,
)
},
)
const { data, hasNextPage, fetchNextPage, isLoading, isFetchingNextPage, refetch } = useInfiniteQuery({
const {
data: records = EMPTY_LIST,
isLoading: recordsLoading,
refetch: refetchRecords,
} = useQuery(['relation-records', currentPersona?.identifier.rawPublicKey], async () => {
return Services.Identity.queryRelationPaged(
currentPersona?.identifier,
{
network: 'all',
pageOffset: 0,
},
3000,
)
})
const {
data,
hasNextPage,
fetchNextPage,
isLoading,
isFetchingNextPage,
refetch: refetchFriends,
} = useInfiniteQuery({
queryKey: ['friends', currentPersona?.identifier.rawPublicKey],
enabled: !recordsLoading,
queryFn: async ({ pageParam = 0 }) => {
Expand Down Expand Up @@ -61,6 +70,10 @@ export function useFriendsPaged() {
return nextPageOffset
},
})
const refetch = useCallback(() => {
refetchFriends()
refetchRecords()
}, [refetchFriends, refetchRecords])
return {
data,
isLoading: isLoading || recordsLoading,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { memo, useState, useCallback } from 'react'
import { memo, useState, useCallback, useMemo } from 'react'
import { Icons } from '@masknet/icons'
import { makeStyles, usePopupCustomSnackbar } from '@masknet/theme'
import { Box, Typography, Link, useTheme, ButtonBase as Button, Avatar } from '@mui/material'
Expand All @@ -18,9 +18,9 @@ import { useI18N } from '../../../../../utils/i18n-next-ui.js'
import Services from '../../../../service.js'
import { useEverSeen } from '@masknet/shared-base-ui'
import { useFriendProfiles } from '../../../hook/useFriendProfiles.js'
import { type UseQueryResult, type RefetchOptions } from '@tanstack/react-query'
import { useQueryClient } from '@tanstack/react-query'
import { useMutation, useQueryClient, type InfiniteData } from '@tanstack/react-query'
import urlcat from 'urlcat'
import { type Friend } from '../../../hook/useFriends.js'

const useStyles = makeStyles()((theme) => ({
card: {
Expand Down Expand Up @@ -77,7 +77,7 @@ interface ContactCardProps {
publicKey?: string
isLocal?: boolean
profile?: ProfileIdentifier
refetch?: (options?: RefetchOptions) => Promise<UseQueryResult>
refetch?: () => void
}

export const ContactCard = memo<ContactCardProps>(function ContactCard({
Expand All @@ -98,31 +98,81 @@ export const ContactCard = memo<ContactCardProps>(function ContactCard({
const { currentPersona } = PersonaContext.useContainer()
const { t } = useI18N()
const profiles = useFriendProfiles(seen, nextId, profile?.userId)
const rawPublicKey = currentPersona?.identifier.rawPublicKey
const queryClient = useQueryClient()
const handleAddFriend = useCallback(async () => {
if (!currentPersona) return
const twitter = profiles?.find((p) => p.platform === NextIDPlatform.Twitter)

const friendInfo = useMemo(() => {
if (!rawPublicKey) return
const twitter = proofProfiles?.find((p) => p.platform === NextIDPlatform.Twitter)
const personaIdentifier = ECKeyIdentifier.fromHexPublicKeyK256(nextId).expect(
`${nextId} should be a valid hex public key in k256`,
)
const rawPublicKey = currentPersona.identifier.rawPublicKey
if (!twitter) {
await Services.Identity.createNewRelation(personaIdentifier, currentPersona.identifier)
return {
persona: personaIdentifier,
}
} else {
const profileIdentifier = ProfileIdentifier.of('twitter.com', twitter.identity).unwrap()
return {
persona: personaIdentifier,
profile: profileIdentifier,
}
}
}, [profiles, nextId, rawPublicKey])

const handleAddFriend = useCallback(async () => {
if (!friendInfo || !currentPersona) return
const { persona, profile } = friendInfo
if (!profile) {
await Services.Identity.createNewRelation(persona, currentPersona.identifier)
} else {
await attachNextIDToProfile({
identifier: profileIdentifier,
linkedPersona: personaIdentifier,
identifier: profile,
linkedPersona: persona,
fromNextID: true,
linkedTwitterNames: twitter ? [twitter.identity] : [],
linkedTwitterNames: [profile.userId],
})
}
showSnackbar(t('popups_encrypted_friends_added_successfully'), { variant: 'success' })
setLocal(true)
queryClient.invalidateQueries(['relation-records', rawPublicKey])
queryClient.invalidateQueries(['friends', rawPublicKey])
refetch?.()
}, [profiles, nextId, currentPersona, queryClient])
}, [nextId, queryClient, currentPersona, refetch, friendInfo])

const { mutate: onAdd, isLoading } = useMutation({
mutationFn: handleAddFriend,
onMutate: async (friend: Friend | undefined) => {
if (!friend) return
await queryClient.cancelQueries(['relation-records', rawPublicKey])
await queryClient.cancelQueries(['friends', rawPublicKey])
const old = queryClient.getQueryData(['friends', rawPublicKey])
queryClient.setQueryData(
['friends', rawPublicKey],
(
oldData:
| InfiniteData<{
friends: Friend[]
nextPageOffset: number
}>
| undefined,
) => {
if (!oldData) return undefined
return {
...oldData,
pages: oldData.pages[0]
? [
{ friends: [friend, ...oldData.pages[0].friends], nextPageOffset: 10 },
...oldData.pages.slice(1),
]
: [{ friends: [friend], nextPageOffset: 0 }],
}
},
)
showSnackbar(t('popups_encrypted_friends_added_successfully'), { variant: 'success' })
setLocal(true)
},
onSettled: async () => {
await queryClient.invalidateQueries(['relation-records', rawPublicKey])
await queryClient.invalidateQueries(['friends', rawPublicKey])
refetch?.()
},
})

return (
<Box className={classes.card} ref={ref}>
Expand Down Expand Up @@ -175,7 +225,7 @@ export const ContactCard = memo<ContactCardProps>(function ContactCard({
<Icons.ArrowRight />
</Button>
) : (
<Button className={classes.addButton} onClick={handleAddFriend}>
<Button className={classes.addButton} onClick={() => onAdd(friendInfo)} disabled={isLoading}>
{t('popups_encrypted_friends_add_friends')}
</Button>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Box } from '@mui/material'
import { useI18N } from '../../../../../utils/i18n-next-ui.js'
import { type Friend } from '../../../hook/useFriends.js'
import { ContactCard } from '../ContactCard/index.js'
import { first } from 'lodash-es'

const useStyles = makeStyles()((theme) => ({
empty: {
Expand Down Expand Up @@ -40,7 +41,7 @@ export interface ContactsProps {
export const Contacts = memo<ContactsProps>(function Contacts({ friendsArray, fetchNextPage }) {
const { classes } = useStyles()
const { t } = useI18N()
return friendsArray.length === 0 ? (
return !first(friendsArray) || first(friendsArray)?.friends.length === 0 ? (
<EmptyStatus className={classes.empty}>{t('popups_encrypted_friends_no_friends')}</EmptyStatus>
) : (
<Box className={classes.cardContainer}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export interface FriendsDetailUIProps {
publicKey?: string
isLocal?: boolean
onDelete: () => void
deleting?: boolean
}

export const FriendsDetailUI = memo<FriendsDetailUIProps>(function FriendsDetailUI({
Expand All @@ -90,6 +91,7 @@ export const FriendsDetailUI = memo<FriendsDetailUIProps>(function FriendsDetail
profiles,
isLocal,
onDelete,
deleting,
}) {
const { classes } = useStyles()
const navigate = useNavigate()
Expand All @@ -105,7 +107,7 @@ export const FriendsDetailUI = memo<FriendsDetailUIProps>(function FriendsDetail
</button>
<Box />
{isLocal ? (
<button onClick={onDelete} type="submit" className={classes.back}>
<button onClick={onDelete} type="submit" className={classes.back} disabled={deleting}>
<Icons.Delete />
</button>
) : null}
Expand Down
50 changes: 43 additions & 7 deletions packages/mask/src/extension/popups/pages/Friends/Detail/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import { useCurrentPersona } from '../../../../../components/DataSource/useCurre
import { useI18N } from '../../../../../utils/i18n-next-ui.js'
import Services from '../../../../service.js'
import { FriendsDetailUI } from './UI.js'
import { useQueryClient } from '@tanstack/react-query'
import { useQueryClient, useMutation, type InfiniteData } from '@tanstack/react-query'
import { usePopupCustomSnackbar } from '@masknet/theme'
import { type Friend } from '../../../hook/useFriends.js'

export const FriendsDetail = memo(function FriendsDetail() {
const location = useLocation()
Expand All @@ -17,17 +18,51 @@ export const FriendsDetail = memo(function FriendsDetail() {
const currentPersona = useCurrentPersona()
const rawPublicKey = currentPersona?.identifier.rawPublicKey
const queryClient = useQueryClient()

const handleDelete = useCallback(async () => {
const personaIdentifier = ECKeyIdentifier.fromHexPublicKeyK256(nextId).expect(
`${nextId} should be a valid hex public key in k256`,
)
if (currentPersona) await Services.Identity.deletePersonaRelation(personaIdentifier, currentPersona?.identifier)
await Services.Identity.deletePersona(personaIdentifier, 'safe delete')
showSnackbar(t('popups_encrypted_friends_deleted_successfully'), { variant: 'success' })
setDeleted(true)
queryClient.invalidateQueries(['relation-records', rawPublicKey])
queryClient.invalidateQueries(['friends', rawPublicKey])
}, [nextId, rawPublicKey, queryClient])
}, [nextId, queryClient, currentPersona])

const { mutate: onDelete, isLoading } = useMutation({
mutationFn: handleDelete,
onMutate: async () => {
await queryClient.cancelQueries(['relation-records', rawPublicKey])
await queryClient.cancelQueries(['friends', rawPublicKey])
queryClient.setQueryData(
['friends', rawPublicKey],
(
oldData:
| InfiniteData<{
friends: Friend[]
nextPageOffset: number
}>
| undefined,
) => {
if (!oldData) return undefined
return {
...oldData,

pages: oldData.pages.map((page) => {
return {
friends: page.friends.filter((friend) => friend.persona.publicKeyAsHex !== nextId),
nextPageOffset: page.nextPageOffset,
}
}),
}
},
)
showSnackbar(t('popups_encrypted_friends_deleted_successfully'), { variant: 'success' })
setDeleted(true)
},
onSettled: () => {
queryClient.invalidateQueries(['relation-records', rawPublicKey])
queryClient.invalidateQueries(['friends', rawPublicKey])
},
})

return (
<FriendsDetailUI
Expand All @@ -36,7 +71,8 @@ export const FriendsDetail = memo(function FriendsDetail() {
nextId={nextId}
publicKey={publicKey}
isLocal={isLocal ? !deleted : false}
onDelete={handleDelete}
onDelete={onDelete}
deleting={isLoading}
/>
)
})
3 changes: 1 addition & 2 deletions packages/mask/src/extension/popups/pages/Friends/Home/UI.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import type { NextIDPersonaBindingsWithIdentifier } from '../../../hook/useFrien
import { Contacts } from '../Contacts/index.js'
import { SearchList } from '../SearchList/index.js'
import { type Friend } from '../../../hook/useFriends.js'
import { type UseQueryResult, type RefetchOptions } from '@tanstack/react-query'

const useStyles = makeStyles()((theme) => ({
container: {
Expand Down Expand Up @@ -45,7 +44,7 @@ export interface FriendsHomeUIProps {
setSearchValue: (v: string) => void
fetchNextPage: () => void
fetchNextSearchPage: () => void
refetch: (options?: RefetchOptions) => Promise<UseQueryResult>
refetch: () => void
}

export const FriendsHomeUI = memo<FriendsHomeUIProps>(function FriendsHomeUI({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { Box } from '@mui/material'
import { useI18N } from '../../../../../utils/i18n-next-ui.js'
import { EmptyStatus, RestorableScroll, ElementAnchor } from '@masknet/shared'
import type { NextIDPersonaBindingsWithIdentifier } from '../../../hook/useFriendsFromSearch.js'
import { type UseQueryResult, type RefetchOptions } from '@tanstack/react-query'

const useStyles = makeStyles()((theme) => ({
empty: {
Expand Down Expand Up @@ -36,10 +35,10 @@ const useStyles = makeStyles()((theme) => ({
export interface SearchListProps {
searchResult: NextIDPersonaBindingsWithIdentifier[]
fetchNextPage: () => void
refetch: (options: RefetchOptions) => Promise<UseQueryResult>
refetch: () => void
}

export const SearchList = memo<SearchListProps>(function SearchList({ searchResult, fetchNextPage }) {
export const SearchList = memo<SearchListProps>(function SearchList({ searchResult, fetchNextPage, refetch }) {
const { classes } = useStyles()
const { t } = useI18N()
return searchResult.length === 0 ? (
Expand All @@ -55,6 +54,7 @@ export const SearchList = memo<SearchListProps>(function SearchList({ searchResu
proofProfiles={friend.proofs}
publicKey={friend.linkedPersona?.rawPublicKey}
isLocal={friend.isLocal}
refetch={refetch}
/>
)
})}
Expand Down

0 comments on commit cfb47ba

Please sign in to comment.