chore(refactor): use NDKContext in profile page
This commit is contained in:
parent
3c061d5920
commit
2248128001
@ -198,7 +198,9 @@ export const NDKContextProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
const user = new NDKUser({ npub })
|
const user = new NDKUser({ npub })
|
||||||
user.ndk = ndk
|
user.ndk = ndk
|
||||||
|
|
||||||
return await user.fetchProfile()
|
return await user.fetchProfile({
|
||||||
|
cacheUsage: NDKSubscriptionCacheUsage.PARALLEL
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const publish = async (
|
const publish = async (
|
||||||
|
@ -1,48 +1,49 @@
|
|||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { Link, useNavigate, useParams } from 'react-router-dom'
|
||||||
|
import { toast } from 'react-toastify'
|
||||||
|
|
||||||
import ContentCopyIcon from '@mui/icons-material/ContentCopy'
|
import ContentCopyIcon from '@mui/icons-material/ContentCopy'
|
||||||
import EditIcon from '@mui/icons-material/Edit'
|
import EditIcon from '@mui/icons-material/Edit'
|
||||||
import { Box, IconButton, SxProps, Theme, Typography } from '@mui/material'
|
import { Box, IconButton, SxProps, Theme, Typography } from '@mui/material'
|
||||||
import { Event, VerifiedEvent, kinds, nip19 } from 'nostr-tools'
|
|
||||||
import { useEffect, useMemo, useState } from 'react'
|
import { nip19 } from 'nostr-tools'
|
||||||
import { useAppSelector } from '../../hooks/store'
|
|
||||||
import { Link, useNavigate, useParams } from 'react-router-dom'
|
import { Container } from '../../components/Container'
|
||||||
import { toast } from 'react-toastify'
|
import { Footer } from '../../components/Footer/Footer'
|
||||||
import { LoadingSpinner } from '../../components/LoadingSpinner'
|
import { LoadingSpinner } from '../../components/LoadingSpinner'
|
||||||
import { MetadataController } from '../../controllers'
|
import { useAppSelector } from '../../hooks/store'
|
||||||
|
|
||||||
import { getProfileSettingsRoute } from '../../routes'
|
import { getProfileSettingsRoute } from '../../routes'
|
||||||
import { NostrJoiningBlock, ProfileMetadata } from '../../types'
|
|
||||||
import {
|
import {
|
||||||
getNostrJoiningBlockNumber,
|
|
||||||
getProfileUsername,
|
getProfileUsername,
|
||||||
getRoboHashPicture,
|
getRoboHashPicture,
|
||||||
hexToNpub,
|
hexToNpub,
|
||||||
shorten
|
shorten
|
||||||
} from '../../utils'
|
} from '../../utils'
|
||||||
|
|
||||||
|
import { NDKEvent, NDKUserProfile, profileFromEvent } from '@nostr-dev-kit/ndk'
|
||||||
|
import { useNDKContext } from '../../hooks'
|
||||||
import styles from './style.module.scss'
|
import styles from './style.module.scss'
|
||||||
import { Container } from '../../components/Container'
|
|
||||||
import { Footer } from '../../components/Footer/Footer'
|
|
||||||
|
|
||||||
export const ProfilePage = () => {
|
export const ProfilePage = () => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
||||||
const { npub } = useParams()
|
const { npub } = useParams()
|
||||||
|
const { ndk, findMetadata } = useNDKContext()
|
||||||
const metadataController = useMemo(() => MetadataController.getInstance(), [])
|
|
||||||
|
|
||||||
const [pubkey, setPubkey] = useState<string>()
|
const [pubkey, setPubkey] = useState<string>()
|
||||||
const [nostrJoiningBlock, setNostrJoiningBlock] =
|
const [userProfile, setUserProfile] = useState<NDKUserProfile | null>(null)
|
||||||
useState<NostrJoiningBlock | null>(null)
|
|
||||||
const [profileMetadata, setProfileMetadata] = useState<ProfileMetadata>()
|
const userRobotImage = useAppSelector((state) => state.userRobotImage)
|
||||||
const metadataState = useAppSelector((state) => state.metadata)
|
const metadataState = useAppSelector((state) => state.metadata)
|
||||||
const { usersPubkey } = useAppSelector((state) => state.auth)
|
const { usersPubkey } = useAppSelector((state) => state.auth)
|
||||||
const userRobotImage = useAppSelector((state) => state.userRobotImage)
|
|
||||||
|
|
||||||
const [isUsersOwnProfile, setIsUsersOwnProfile] = useState(false)
|
const [isUsersOwnProfile, setIsUsersOwnProfile] = useState(false)
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(true)
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
const [loadingSpinnerDesc] = useState('Fetching metadata')
|
const [loadingSpinnerDesc] = useState('Fetching metadata')
|
||||||
|
|
||||||
const profileName = pubkey && getProfileUsername(pubkey, profileMetadata)
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (npub) {
|
if (npub) {
|
||||||
try {
|
try {
|
||||||
@ -57,60 +58,30 @@ export const ProfilePage = () => {
|
|||||||
}, [npub, usersPubkey])
|
}, [npub, usersPubkey])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (pubkey) {
|
|
||||||
getNostrJoiningBlockNumber(pubkey)
|
|
||||||
.then((res) => {
|
|
||||||
setNostrJoiningBlock(res)
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
// todo: handle error
|
|
||||||
console.log('err :>> ', err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isUsersOwnProfile && metadataState) {
|
if (isUsersOwnProfile && metadataState) {
|
||||||
const metadataContent = metadataController.extractProfileMetadataContent(
|
const ndkEvent = new NDKEvent(ndk, metadataState)
|
||||||
metadataState as VerifiedEvent
|
const profile = profileFromEvent(ndkEvent)
|
||||||
)
|
|
||||||
if (metadataContent) {
|
setUserProfile(profile)
|
||||||
setProfileMetadata(metadataContent)
|
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
}
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pubkey) {
|
if (pubkey) {
|
||||||
const getMetadata = async (pubkey: string) => {
|
findMetadata(pubkey)
|
||||||
const handleMetadataEvent = (event: Event) => {
|
.then((profile) => {
|
||||||
const metadataContent =
|
setUserProfile(profile)
|
||||||
metadataController.extractProfileMetadataContent(event)
|
})
|
||||||
if (metadataContent) {
|
.catch((err) => {
|
||||||
setProfileMetadata(metadataContent)
|
toast.error(err)
|
||||||
}
|
})
|
||||||
}
|
.finally(() => {
|
||||||
|
setIsLoading(false)
|
||||||
metadataController.on(pubkey, (kind: number, event: Event) => {
|
|
||||||
if (kind === kinds.Metadata) {
|
|
||||||
handleMetadataEvent(event)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const metadataEvent = await metadataController
|
|
||||||
.findMetadata(pubkey)
|
|
||||||
.catch((err) => {
|
|
||||||
toast.error(err)
|
|
||||||
return null
|
|
||||||
})
|
|
||||||
|
|
||||||
if (metadataEvent) handleMetadataEvent(metadataEvent)
|
|
||||||
|
|
||||||
setIsLoading(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
getMetadata(pubkey)
|
|
||||||
}
|
}
|
||||||
}, [isUsersOwnProfile, metadataState, pubkey, metadataController])
|
}, [ndk, isUsersOwnProfile, metadataState, pubkey, findMetadata])
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rendering text with button which copies the provided text
|
* Rendering text with button which copies the provided text
|
||||||
@ -146,29 +117,32 @@ export const ProfilePage = () => {
|
|||||||
*
|
*
|
||||||
* @returns robohash image url
|
* @returns robohash image url
|
||||||
*/
|
*/
|
||||||
const getProfileImage = (metadata: ProfileMetadata) => {
|
const getProfileImage = (profile: NDKUserProfile | null) => {
|
||||||
if (!metadata) return ''
|
if (!profile) return getRoboHashPicture(npub)
|
||||||
|
|
||||||
if (!isUsersOwnProfile) {
|
if (!isUsersOwnProfile) {
|
||||||
return metadata.picture || getRoboHashPicture(npub!)
|
return profile.image || getRoboHashPicture(npub!)
|
||||||
}
|
}
|
||||||
|
|
||||||
// userRobotImage is used only when visiting own profile
|
// userRobotImage is used only when visiting own profile
|
||||||
// while kind 0 picture is not set
|
// while kind 0 picture is not set
|
||||||
return metadata.picture || userRobotImage || getRoboHashPicture(npub!)
|
return profile.image || userRobotImage || getRoboHashPicture(npub!)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const profileName =
|
||||||
|
pubkey && getProfileUsername(pubkey, userProfile || undefined)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isLoading && <LoadingSpinner desc={loadingSpinnerDesc} />}
|
{isLoading && <LoadingSpinner desc={loadingSpinnerDesc} />}
|
||||||
{pubkey && (
|
{pubkey && (
|
||||||
<Container className={styles.container}>
|
<Container className={styles.container}>
|
||||||
<Box
|
<Box
|
||||||
className={`${styles.banner} ${!profileMetadata || !profileMetadata.banner ? styles.noImage : ''}`}
|
className={`${styles.banner} ${!userProfile || !userProfile.banner ? styles.noImage : ''}`}
|
||||||
>
|
>
|
||||||
{profileMetadata && profileMetadata.banner ? (
|
{userProfile && userProfile.banner ? (
|
||||||
<img
|
<img
|
||||||
src={profileMetadata.banner}
|
src={userProfile.banner}
|
||||||
alt={`banner image for ${profileName}`}
|
alt={`banner image for ${profileName}`}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
@ -189,24 +163,12 @@ export const ProfilePage = () => {
|
|||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
className={styles['image-placeholder']}
|
className={styles['image-placeholder']}
|
||||||
src={getProfileImage(profileMetadata!)}
|
src={getProfileImage(userProfile)}
|
||||||
alt={profileName}
|
alt={profileName}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Box>
|
</Box>
|
||||||
<Box className={styles.middle}>
|
|
||||||
<Typography
|
|
||||||
component={Link}
|
|
||||||
to={`https://njump.me/${nostrJoiningBlock?.encodedEventPointer || ''}`}
|
|
||||||
target="_blank"
|
|
||||||
className={`${styles.nostrSince} ${styles.link}`}
|
|
||||||
variant="caption"
|
|
||||||
>
|
|
||||||
{nostrJoiningBlock
|
|
||||||
? `On nostr since ${nostrJoiningBlock.block.toLocaleString()}`
|
|
||||||
: 'On nostr since: unknown'}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
<Box className={styles.right}>
|
<Box className={styles.right}>
|
||||||
{isUsersOwnProfile && (
|
{isUsersOwnProfile && (
|
||||||
<IconButton
|
<IconButton
|
||||||
@ -224,15 +186,13 @@ export const ProfilePage = () => {
|
|||||||
display: 'flex'
|
display: 'flex'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{profileMetadata && (
|
<Typography
|
||||||
<Typography
|
sx={{ margin: '5px 0 5px 0' }}
|
||||||
sx={{ margin: '5px 0 5px 0' }}
|
variant="h6"
|
||||||
variant="h6"
|
className={styles.bold}
|
||||||
className={styles.bold}
|
>
|
||||||
>
|
{profileName}
|
||||||
{profileName}
|
</Typography>
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
{textElementWithCopyIcon(
|
{textElementWithCopyIcon(
|
||||||
@ -242,42 +202,34 @@ export const ProfilePage = () => {
|
|||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
{profileMetadata?.nip05 &&
|
{userProfile?.nip05 &&
|
||||||
textElementWithCopyIcon(
|
textElementWithCopyIcon(userProfile.nip05, undefined, 15)}
|
||||||
profileMetadata.nip05,
|
|
||||||
undefined,
|
|
||||||
15
|
|
||||||
)}
|
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
{profileMetadata?.lud16 &&
|
{userProfile?.lud16 &&
|
||||||
textElementWithCopyIcon(
|
textElementWithCopyIcon(userProfile.lud16, undefined, 15)}
|
||||||
profileMetadata.lud16,
|
|
||||||
undefined,
|
|
||||||
15
|
|
||||||
)}
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
{profileMetadata?.website && (
|
{userProfile?.website && (
|
||||||
<Typography
|
<Typography
|
||||||
sx={{ marginTop: '10px' }}
|
sx={{ marginTop: '10px' }}
|
||||||
variant="caption"
|
variant="caption"
|
||||||
component={Link}
|
component={Link}
|
||||||
to={profileMetadata.website}
|
to={userProfile.website}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className={`${styles.website} ${styles.link} ${styles.captionWrapper}`}
|
className={`${styles.website} ${styles.link} ${styles.captionWrapper}`}
|
||||||
>
|
>
|
||||||
{profileMetadata.website}
|
{userProfile.website}
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
{profileMetadata?.about && (
|
{userProfile?.about && (
|
||||||
<Typography mt={1} className={styles.about}>
|
<Typography mt={1} className={styles.about}>
|
||||||
{profileMetadata.about}
|
{userProfile.about}
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -1,4 +1,11 @@
|
|||||||
|
import React, { useEffect, useRef, useState } from 'react'
|
||||||
|
import { useParams } from 'react-router-dom'
|
||||||
|
import { toast } from 'react-toastify'
|
||||||
|
|
||||||
|
import { SmartToy } from '@mui/icons-material'
|
||||||
import ContentCopyIcon from '@mui/icons-material/ContentCopy'
|
import ContentCopyIcon from '@mui/icons-material/ContentCopy'
|
||||||
|
import LaunchIcon from '@mui/icons-material/Launch'
|
||||||
|
import { LoadingButton } from '@mui/lab'
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
IconButton,
|
IconButton,
|
||||||
@ -7,59 +14,53 @@ import {
|
|||||||
ListItem,
|
ListItem,
|
||||||
ListSubheader,
|
ListSubheader,
|
||||||
TextField,
|
TextField,
|
||||||
Tooltip,
|
Tooltip
|
||||||
Typography,
|
|
||||||
useTheme
|
|
||||||
} from '@mui/material'
|
} from '@mui/material'
|
||||||
import { UnsignedEvent, nip19, kinds, VerifiedEvent, Event } from 'nostr-tools'
|
|
||||||
import React, { useEffect, useRef, useState } from 'react'
|
import {
|
||||||
import { Link, useParams } from 'react-router-dom'
|
NDKEvent,
|
||||||
import { toast } from 'react-toastify'
|
NDKUserProfile,
|
||||||
import { MetadataController, NostrController } from '../../../controllers'
|
profileFromEvent,
|
||||||
import { NostrJoiningBlock, ProfileMetadata } from '../../../types'
|
serializeProfile
|
||||||
import styles from './style.module.scss'
|
} from '@nostr-dev-kit/ndk'
|
||||||
|
import { launch as launchNostrLoginDialog } from 'nostr-login'
|
||||||
|
import { kinds, nip19, UnsignedEvent } from 'nostr-tools'
|
||||||
|
|
||||||
|
import { NostrController } from '../../../controllers'
|
||||||
|
|
||||||
|
import { useNDKContext } from '../../../hooks'
|
||||||
import { useAppDispatch, useAppSelector } from '../../../hooks/store'
|
import { useAppDispatch, useAppSelector } from '../../../hooks/store'
|
||||||
|
|
||||||
import { LoadingButton } from '@mui/lab'
|
import { getRoboHashPicture, unixNow } from '../../../utils'
|
||||||
import { Dispatch } from '../../../store/store'
|
|
||||||
import { setMetadataEvent } from '../../../store/actions'
|
|
||||||
import { LoadingSpinner } from '../../../components/LoadingSpinner'
|
|
||||||
import { LoginMethod, NostrLoginAuthMethod } from '../../../store/auth/types'
|
|
||||||
import { SmartToy } from '@mui/icons-material'
|
|
||||||
import {
|
|
||||||
getNostrJoiningBlockNumber,
|
|
||||||
getRoboHashPicture,
|
|
||||||
unixNow
|
|
||||||
} from '../../../utils'
|
|
||||||
import { Container } from '../../../components/Container'
|
import { Container } from '../../../components/Container'
|
||||||
import { Footer } from '../../../components/Footer/Footer'
|
import { Footer } from '../../../components/Footer/Footer'
|
||||||
import LaunchIcon from '@mui/icons-material/Launch'
|
import { LoadingSpinner } from '../../../components/LoadingSpinner'
|
||||||
import { launch as launchNostrLoginDialog } from 'nostr-login'
|
|
||||||
|
import { setMetadataEvent } from '../../../store/actions'
|
||||||
|
import { LoginMethod, NostrLoginAuthMethod } from '../../../store/auth/types'
|
||||||
|
import { Dispatch } from '../../../store/store'
|
||||||
|
|
||||||
|
import styles from './style.module.scss'
|
||||||
|
|
||||||
export const ProfileSettingsPage = () => {
|
export const ProfileSettingsPage = () => {
|
||||||
const theme = useTheme()
|
|
||||||
|
|
||||||
const { npub } = useParams()
|
|
||||||
|
|
||||||
const dispatch: Dispatch = useAppDispatch()
|
const dispatch: Dispatch = useAppDispatch()
|
||||||
|
|
||||||
const metadataController = MetadataController.getInstance()
|
const { npub } = useParams()
|
||||||
const nostrController = NostrController.getInstance()
|
const { ndk, findMetadata, publish } = useNDKContext()
|
||||||
|
|
||||||
const [pubkey, setPubkey] = useState<string>()
|
const [pubkey, setPubkey] = useState<string>()
|
||||||
const [nostrJoiningBlock, setNostrJoiningBlock] =
|
const [userProfile, setUserProfile] = useState<NDKUserProfile | null>(null)
|
||||||
useState<NostrJoiningBlock | null>(null)
|
|
||||||
const [profileMetadata, setProfileMetadata] = useState<ProfileMetadata>()
|
const userRobotImage = useAppSelector((state) => state.userRobotImage)
|
||||||
const [savingProfileMetadata, setSavingProfileMetadata] = useState(false)
|
|
||||||
const metadataState = useAppSelector((state) => state.metadata)
|
const metadataState = useAppSelector((state) => state.metadata)
|
||||||
const keys = useAppSelector((state) => state.auth?.keyPair)
|
const keys = useAppSelector((state) => state.auth?.keyPair)
|
||||||
const { usersPubkey, loginMethod, nostrLoginAuthMethod } = useAppSelector(
|
const { usersPubkey, loginMethod, nostrLoginAuthMethod } = useAppSelector(
|
||||||
(state) => state.auth
|
(state) => state.auth
|
||||||
)
|
)
|
||||||
const userRobotImage = useAppSelector((state) => state.userRobotImage)
|
|
||||||
|
|
||||||
|
const [savingProfileMetadata, setSavingProfileMetadata] = useState(false)
|
||||||
const [isUsersOwnProfile, setIsUsersOwnProfile] = useState(false)
|
const [isUsersOwnProfile, setIsUsersOwnProfile] = useState(false)
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(true)
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
const [loadingSpinnerDesc] = useState('Fetching metadata')
|
const [loadingSpinnerDesc] = useState('Fetching metadata')
|
||||||
|
|
||||||
@ -79,63 +80,33 @@ export const ProfileSettingsPage = () => {
|
|||||||
}, [npub, usersPubkey])
|
}, [npub, usersPubkey])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (pubkey) {
|
|
||||||
getNostrJoiningBlockNumber(pubkey)
|
|
||||||
.then((res) => {
|
|
||||||
setNostrJoiningBlock(res)
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
// todo: handle error
|
|
||||||
console.log('err :>> ', err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isUsersOwnProfile && metadataState) {
|
if (isUsersOwnProfile && metadataState) {
|
||||||
const metadataContent = metadataController.extractProfileMetadataContent(
|
const ndkEvent = new NDKEvent(ndk, metadataState)
|
||||||
metadataState as VerifiedEvent
|
const profile = profileFromEvent(ndkEvent)
|
||||||
)
|
|
||||||
if (metadataContent) {
|
setUserProfile(profile)
|
||||||
setProfileMetadata(metadataContent)
|
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
}
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pubkey) {
|
if (pubkey) {
|
||||||
const getMetadata = async (pubkey: string) => {
|
findMetadata(pubkey)
|
||||||
const handleMetadataEvent = (event: Event) => {
|
.then((profile) => {
|
||||||
const metadataContent =
|
setUserProfile(profile)
|
||||||
metadataController.extractProfileMetadataContent(event)
|
})
|
||||||
if (metadataContent) {
|
.catch((err) => {
|
||||||
setProfileMetadata(metadataContent)
|
toast.error(err)
|
||||||
}
|
})
|
||||||
}
|
.finally(() => {
|
||||||
|
setIsLoading(false)
|
||||||
metadataController.on(pubkey, (kind: number, event: Event) => {
|
|
||||||
if (kind === kinds.Metadata) {
|
|
||||||
handleMetadataEvent(event)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const metadataEvent = await metadataController
|
|
||||||
.findMetadata(pubkey)
|
|
||||||
.catch((err) => {
|
|
||||||
toast.error(err)
|
|
||||||
return null
|
|
||||||
})
|
|
||||||
|
|
||||||
if (metadataEvent) handleMetadataEvent(metadataEvent)
|
|
||||||
|
|
||||||
setIsLoading(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
getMetadata(pubkey)
|
|
||||||
}
|
}
|
||||||
}, [isUsersOwnProfile, metadataState, pubkey, metadataController])
|
}, [ndk, isUsersOwnProfile, metadataState, pubkey, findMetadata])
|
||||||
|
|
||||||
const editItem = (
|
const editItem = (
|
||||||
key: keyof ProfileMetadata,
|
key: keyof NDKUserProfile,
|
||||||
label: string,
|
label: string,
|
||||||
multiline = false,
|
multiline = false,
|
||||||
rows = 1,
|
rows = 1,
|
||||||
@ -145,7 +116,7 @@ export const ProfileSettingsPage = () => {
|
|||||||
<TextField
|
<TextField
|
||||||
label={label}
|
label={label}
|
||||||
id={label.split(' ').join('-')}
|
id={label.split(' ').join('-')}
|
||||||
value={profileMetadata![key] || ''}
|
value={userProfile![key] || ''}
|
||||||
size="small"
|
size="small"
|
||||||
multiline={multiline}
|
multiline={multiline}
|
||||||
rows={rows}
|
rows={rows}
|
||||||
@ -155,7 +126,7 @@ export const ProfileSettingsPage = () => {
|
|||||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
|
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const { value } = event.target
|
const { value } = event.target
|
||||||
|
|
||||||
setProfileMetadata((prev) => ({
|
setUserProfile((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
[key]: value
|
[key]: value
|
||||||
}))
|
}))
|
||||||
@ -197,32 +168,45 @@ export const ProfileSettingsPage = () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const handleSaveMetadata = async () => {
|
const handleSaveMetadata = async () => {
|
||||||
|
if (!userProfile) return
|
||||||
|
|
||||||
setSavingProfileMetadata(true)
|
setSavingProfileMetadata(true)
|
||||||
|
|
||||||
const content = JSON.stringify(profileMetadata)
|
const serializedProfile = serializeProfile(userProfile)
|
||||||
|
|
||||||
// We need to omit cachedAt and create new event
|
const unsignedEvent: UnsignedEvent = {
|
||||||
// Relay will reject if created_at is too late
|
content: serializedProfile,
|
||||||
const updatedMetadataState: UnsignedEvent = {
|
|
||||||
content: content,
|
|
||||||
created_at: unixNow(),
|
created_at: unixNow(),
|
||||||
kind: kinds.Metadata,
|
kind: kinds.Metadata,
|
||||||
pubkey: pubkey!,
|
pubkey: pubkey!,
|
||||||
tags: metadataState?.tags || []
|
tags: []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const nostrController = NostrController.getInstance()
|
||||||
const signedEvent = await nostrController
|
const signedEvent = await nostrController
|
||||||
.signEvent(updatedMetadataState)
|
.signEvent(unsignedEvent)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
toast.error(`Error saving profile metadata. ${error}`)
|
toast.error(`Error saving profile metadata. ${error}`)
|
||||||
|
return null
|
||||||
})
|
})
|
||||||
|
|
||||||
if (signedEvent) {
|
if (!signedEvent) {
|
||||||
if (!metadataController.validate(signedEvent)) {
|
setSavingProfileMetadata(false)
|
||||||
toast.error(`Metadata is not valid.`)
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
await metadataController.publishMetadataEvent(signedEvent)
|
const ndkEvent = new NDKEvent(ndk, signedEvent)
|
||||||
|
const publishedOnRelays = await publish(ndkEvent)
|
||||||
|
|
||||||
|
// Handle cases where publishing failed or succeeded
|
||||||
|
if (publishedOnRelays.length === 0) {
|
||||||
|
toast.error('Failed to publish event on any relay')
|
||||||
|
} else {
|
||||||
|
toast.success(
|
||||||
|
`Event published successfully to the following relays\n\n${publishedOnRelays.join(
|
||||||
|
'\n'
|
||||||
|
)}`
|
||||||
|
)
|
||||||
|
|
||||||
dispatch(setMetadataEvent(signedEvent))
|
dispatch(setMetadataEvent(signedEvent))
|
||||||
}
|
}
|
||||||
@ -241,7 +225,7 @@ export const ProfileSettingsPage = () => {
|
|||||||
|
|
||||||
const robotAvatarLink = getRoboHashPicture(npub!, robotSet.current)
|
const robotAvatarLink = getRoboHashPicture(npub!, robotSet.current)
|
||||||
|
|
||||||
setProfileMetadata((prev) => ({
|
setUserProfile((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
picture: robotAvatarLink
|
picture: robotAvatarLink
|
||||||
}))
|
}))
|
||||||
@ -267,14 +251,14 @@ export const ProfileSettingsPage = () => {
|
|||||||
*
|
*
|
||||||
* @returns robohash image url
|
* @returns robohash image url
|
||||||
*/
|
*/
|
||||||
const getProfileImage = (metadata: ProfileMetadata) => {
|
const getProfileImage = (profile: NDKUserProfile) => {
|
||||||
if (!isUsersOwnProfile) {
|
if (!isUsersOwnProfile) {
|
||||||
return metadata.picture || getRoboHashPicture(npub!)
|
return profile.image || getRoboHashPicture(npub!)
|
||||||
}
|
}
|
||||||
|
|
||||||
// userRobotImage is used only when visiting own profile
|
// userRobotImage is used only when visiting own profile
|
||||||
// while kind 0 picture is not set
|
// while kind 0 picture is not set
|
||||||
return metadata.picture || userRobotImage || getRoboHashPicture(npub!)
|
return profile.image || userRobotImage || getRoboHashPicture(npub!)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -300,7 +284,7 @@ export const ProfileSettingsPage = () => {
|
|||||||
</ListSubheader>
|
</ListSubheader>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{profileMetadata && (
|
{userProfile && (
|
||||||
<div>
|
<div>
|
||||||
<ListItem
|
<ListItem
|
||||||
sx={{
|
sx={{
|
||||||
@ -309,10 +293,10 @@ export const ProfileSettingsPage = () => {
|
|||||||
flexDirection: 'column'
|
flexDirection: 'column'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{profileMetadata.banner ? (
|
{userProfile.banner ? (
|
||||||
<img
|
<img
|
||||||
className={styles.bannerImg}
|
className={styles.bannerImg}
|
||||||
src={profileMetadata.banner}
|
src={userProfile.banner}
|
||||||
alt="Banner Image"
|
alt="Banner Image"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
@ -334,24 +318,9 @@ export const ProfileSettingsPage = () => {
|
|||||||
event.currentTarget.src = getRoboHashPicture(npub!)
|
event.currentTarget.src = getRoboHashPicture(npub!)
|
||||||
}}
|
}}
|
||||||
className={styles.img}
|
className={styles.img}
|
||||||
src={getProfileImage(profileMetadata)}
|
src={getProfileImage(userProfile)}
|
||||||
alt="Profile Image"
|
alt="Profile Image"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{nostrJoiningBlock && (
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
color: theme.palette.getContrastText(
|
|
||||||
theme.palette.background.paper
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
component={Link}
|
|
||||||
to={`https://njump.me/${nostrJoiningBlock.encodedEventPointer}`}
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
On nostr since {nostrJoiningBlock.block.toLocaleString()}
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
{editItem('picture', 'Picture URL', undefined, undefined, {
|
{editItem('picture', 'Picture URL', undefined, undefined, {
|
||||||
@ -368,6 +337,7 @@ export const ProfileSettingsPage = () => {
|
|||||||
<>
|
<>
|
||||||
{usersPubkey &&
|
{usersPubkey &&
|
||||||
copyItem(nip19.npubEncode(usersPubkey), 'Public Key')}
|
copyItem(nip19.npubEncode(usersPubkey), 'Public Key')}
|
||||||
|
|
||||||
{loginMethod === LoginMethod.privateKey &&
|
{loginMethod === LoginMethod.privateKey &&
|
||||||
keys &&
|
keys &&
|
||||||
keys.private &&
|
keys.private &&
|
||||||
|
Loading…
Reference in New Issue
Block a user