fix: #201, #145, #205 and additional fixes #206

Open
enes wants to merge 13 commits from 201-toolbox-update into staging
10 changed files with 308 additions and 228 deletions

View File

@ -28,9 +28,9 @@ import {
} from '../../routes'
import {
clearAuthToken,
getProfileUsername,
hexToNpub,
saveNsecBunkerDelegatedKey,
shorten
saveNsecBunkerDelegatedKey
} from '../../utils'
import styles from './style.module.scss'
import { setUserRobotImage } from '../../store/userRobotImage/action'
@ -58,9 +58,8 @@ export const AppBar = () => {
useEffect(() => {
if (metadataState) {
if (metadataState.content) {
const { picture, display_name, name } = JSON.parse(
metadataState.content
)
const profileMetadata = JSON.parse(metadataState.content)
const { picture } = profileMetadata
if (picture || userRobotImage) {
setUserAvatar(picture || userRobotImage)
@ -70,7 +69,7 @@ export const AppBar = () => {
? hexToNpub(authState.usersPubkey)
: ''
setUsername(shorten(display_name || name || npub, 7))
setUsername(getProfileUsername(npub, profileMetadata))
} else {
setUserAvatar(userRobotImage || '')
setUsername('')

View File

@ -11,16 +11,16 @@ import styles from './style.module.scss'
import React, { useEffect, useState } from 'react'
import { ProfileMetadata, User, UserRole } from '../../types'
import { MouseState, PdfPage, DrawnField, DrawTool } from '../../types/drawing'
import { truncate } from 'lodash'
import { settleAllFullfilfedPromises, hexToNpub, npubToHex } from '../../utils'
import { getSigitFile, SigitFile } from '../../utils/file'
import { hexToNpub, npubToHex, getProfileUsername } from '../../utils'
import { SigitFile } from '../../utils/file'
import { getToolboxLabelByMarkType } from '../../utils/mark'
import { FileDivider } from '../FileDivider'
import { ExtensionFileBox } from '../ExtensionFileBox'
import { FONT_SIZE, FONT_TYPE, inPx } from '../../utils/pdf'
import { useScale } from '../../hooks/useScale'
import { AvatarIconButton } from '../UserAvatarIconButton'
import { LoadingSpinner } from '../LoadingSpinner'
import { UserAvatar } from '../UserAvatar'
import _ from 'lodash'
const DEFAULT_START_SIZE = {
width: 140,
@ -28,52 +28,50 @@ const DEFAULT_START_SIZE = {
} as const
interface Props {
selectedFiles: File[]
users: User[]
metadata: { [key: string]: ProfileMetadata }
onDrawFieldsChange: (sigitFiles: SigitFile[]) => void
sigitFiles: SigitFile[]
setSigitFiles: React.Dispatch<React.SetStateAction<SigitFile[]>>
selectedTool?: DrawTool
}
export const DrawPDFFields = (props: Props) => {
const { selectedFiles, selectedTool, onDrawFieldsChange, users } = props
const { to, from } = useScale()
const { selectedTool, sigitFiles, setSigitFiles, users } = props
const [sigitFiles, setSigitFiles] = useState<SigitFile[]>([])
const [parsingPdf, setIsParsing] = useState<boolean>(false)
const signers = users.filter((u) => u.role === UserRole.signer)
const defaultSignerNpub = signers.length ? hexToNpub(signers[0].pubkey) : ''
const [lastSigner, setLastSigner] = useState(defaultSignerNpub)
/**
* Return first pubkey that is present in the signers list
* @param pubkeys
* @returns available pubkey or empty string
*/
const getAvailableSigner = (...pubkeys: string[]) => {
const availableSigner: string | undefined = pubkeys.find((pubkey) =>
signers.some((s) => s.pubkey === npubToHex(pubkey))
)
return availableSigner || ''
}
const { to, from } = useScale()
const [mouseState, setMouseState] = useState<MouseState>({
clicked: false
})
const [activeDrawField, setActiveDrawField] = useState<number>()
useEffect(() => {
if (selectedFiles) {
/**
* Reads the binary files and converts to internal file type
* and sets to a state (adds images if it's a PDF)
*/
const parsePages = async () => {
const files = await settleAllFullfilfedPromises(
selectedFiles,
getSigitFile
)
setSigitFiles(files)
}
setIsParsing(true)
parsePages().finally(() => {
setIsParsing(false)
})
}
}, [selectedFiles])
useEffect(() => {
if (sigitFiles) onDrawFieldsChange(sigitFiles)
}, [onDrawFieldsChange, sigitFiles])
const [activeDrawnField, setActiveDrawnField] = useState<{
fileIndex: number
pageIndex: number
drawnFieldIndex: number
}>()
const isActiveDrawnField = (
fileIndex: number,
pageIndex: number,
drawnFieldIndex: number
) =>
activeDrawnField?.fileIndex === fileIndex &&
activeDrawnField?.pageIndex === pageIndex &&
activeDrawnField?.drawnFieldIndex === drawnFieldIndex
/**
* Drawing events
@ -100,7 +98,12 @@ export const DrawPDFFields = (props: Props) => {
* @param event Pointer event
* @param page PdfPage where press happened
*/
const handlePointerDown = (event: React.PointerEvent, page: PdfPage) => {
const handlePointerDown = (
event: React.PointerEvent,
page: PdfPage,
fileIndex: number,
pageIndex: number
) => {
// Proceed only if left click
if (event.button !== 0) return
@ -115,12 +118,17 @@ export const DrawPDFFields = (props: Props) => {
top: to(page.width, y),
width: event.pointerType === 'mouse' ? 0 : DEFAULT_START_SIZE.width,
height: event.pointerType === 'mouse' ? 0 : DEFAULT_START_SIZE.height,
counterpart: '',
counterpart: getAvailableSigner(lastSigner, defaultSignerNpub),
type: selectedTool.identifier
}
page.drawnFields.push(newField)
setActiveDrawnField({
fileIndex,
pageIndex,
drawnFieldIndex: page.drawnFields.length - 1
})
setMouseState((prev) => {
return {
...prev,
@ -189,6 +197,8 @@ export const DrawPDFFields = (props: Props) => {
*/
const handleDrawnFieldPointerDown = (
event: React.PointerEvent,
fileIndex: number,
pageIndex: number,
drawnFieldIndex: number
) => {
event.stopPropagation()
@ -198,7 +208,7 @@ export const DrawPDFFields = (props: Props) => {
const drawingRectangleCoords = getPointerCoordinates(event)
setActiveDrawField(drawnFieldIndex)
setActiveDrawnField({ fileIndex, pageIndex, drawnFieldIndex })
setMouseState({
dragging: true,
clicked: false,
@ -254,13 +264,15 @@ export const DrawPDFFields = (props: Props) => {
*/
const handleResizePointerDown = (
event: React.PointerEvent,
fileIndex: number,
pageIndex: number,
drawnFieldIndex: number
) => {
// Proceed only if left click
if (event.button !== 0) return
event.stopPropagation()
setActiveDrawField(drawnFieldIndex)
setActiveDrawnField({ fileIndex, pageIndex, drawnFieldIndex })
setMouseState({
resizing: true
})
@ -369,7 +381,7 @@ export const DrawPDFFields = (props: Props) => {
handlePointerMove(event, page)
}}
onPointerDown={(event) => {
handlePointerDown(event, page)
handlePointerDown(event, page, fileIndex, pageIndex)
}}
draggable="false"
src={page.image}
@ -381,7 +393,12 @@ export const DrawPDFFields = (props: Props) => {
<div
key={drawnFieldIndex}
onPointerDown={(event) =>
handleDrawnFieldPointerDown(event, drawnFieldIndex)
handleDrawnFieldPointerDown(
event,
fileIndex,
pageIndex,
drawnFieldIndex
)
}
onPointerMove={(event) => {
handleDrawnFieldPointerMove(event, drawnField, page.width)
@ -402,7 +419,11 @@ export const DrawPDFFields = (props: Props) => {
touchAction: 'none',
opacity:
mouseState.dragging &&
activeDrawField === drawnFieldIndex
isActiveDrawnField(
fileIndex,
pageIndex,
drawnFieldIndex
)
? 0.8
: undefined
}}
@ -419,7 +440,12 @@ export const DrawPDFFields = (props: Props) => {
</div>
<span
onPointerDown={(event) =>
handleResizePointerDown(event, drawnFieldIndex)
handleResizePointerDown(
event,
fileIndex,
pageIndex,
drawnFieldIndex
)
}
onPointerMove={(event) => {
handleResizePointerMove(event, drawnField, page.width)
@ -428,7 +454,11 @@ export const DrawPDFFields = (props: Props) => {
style={{
background:
mouseState.resizing &&
activeDrawField === drawnFieldIndex
isActiveDrawnField(
fileIndex,
pageIndex,
drawnFieldIndex
)
? 'var(--primary-main)'
: undefined
}}
@ -446,6 +476,23 @@ export const DrawPDFFields = (props: Props) => {
>
<Close fontSize="small" />
</span>
{!isActiveDrawnField(
fileIndex,
pageIndex,
drawnFieldIndex
) &&
!!drawnField.counterpart && (
<div className={styles.counterpartAvatar}>
<UserAvatar
pubkey={npubToHex(drawnField.counterpart)!}
/>
</div>
)}
{isActiveDrawnField(
fileIndex,
pageIndex,
drawnFieldIndex
) && (
<div
onPointerDown={handleUserSelectPointerDown}
className={styles.userSelect}
@ -453,9 +500,10 @@ export const DrawPDFFields = (props: Props) => {
<FormControl fullWidth size="small">
<InputLabel id="counterparts">Counterpart</InputLabel>
<Select
value={drawnField.counterpart}
value={getAvailableSigner(drawnField.counterpart)}
onChange={(event) => {
drawnField.counterpart = event.target.value
setLastSigner(event.target.value)
refreshPdfFiles()
}}
labelId="counterparts"
@ -463,39 +511,24 @@ export const DrawPDFFields = (props: Props) => {
sx={{
background: 'white'
}}
renderValue={(value) => renderCounterpartValue(value)}
renderValue={(value) =>
renderCounterpartValue(value)
}
>
{users
.filter((u) => u.role === UserRole.signer)
.map((user, index) => {
const npub = hexToNpub(user.pubkey)
let displayValue = truncate(npub, {
length: 16
})
const metadata = props.metadata[user.pubkey]
if (metadata) {
displayValue = truncate(
metadata.name ||
metadata.display_name ||
metadata.username ||
{signers.map((signer, index) => {
const npub = hexToNpub(signer.pubkey)
const metadata = props.metadata[signer.pubkey]
const displayValue = getProfileUsername(
npub,
{
length: 16
}
metadata
)
}
return (
<MenuItem
key={index}
value={hexToNpub(user.pubkey)}
>
<MenuItem key={index} value={npub}>
<ListItemIcon>
<AvatarIconButton
src={metadata?.picture}
hexKey={user.pubkey}
hexKey={signer.pubkey}
aria-label={`account of user ${displayValue}`}
color="inherit"
sx={{
@ -514,6 +547,7 @@ export const DrawPDFFields = (props: Props) => {
</Select>
</FormControl>
</div>
)}
</div>
)
})}
@ -524,28 +558,19 @@ export const DrawPDFFields = (props: Props) => {
)
}
const renderCounterpartValue = (value: string) => {
const user = users.find((u) => u.pubkey === npubToHex(value))
if (user) {
let displayValue = truncate(value, {
length: 16
})
const renderCounterpartValue = (npub: string) => {
let displayValue = _.truncate(npub, { length: 16 })
const metadata = props.metadata[user.pubkey]
const signer = signers.find((u) => u.pubkey === npubToHex(npub))
if (signer) {
const metadata = props.metadata[signer.pubkey]
displayValue = getProfileUsername(npub, metadata)
if (metadata) {
displayValue = truncate(
metadata.name || metadata.display_name || metadata.username || value,
{
length: 16
}
)
}
return (
<>
<div className={styles.counterpartSelectValue}>
<AvatarIconButton
src={props.metadata[user.pubkey]?.picture}
hexKey={npubToHex(user.pubkey) || undefined}
src={props.metadata[signer.pubkey]?.picture}
hexKey={signer.pubkey || undefined}
sx={{
padding: 0,
marginRight: '6px',
@ -556,19 +581,11 @@ export const DrawPDFFields = (props: Props) => {
}}
/>
{displayValue}
</>
</div>
)
}
return value
}
if (parsingPdf) {
return <LoadingSpinner variant="small" />
}
if (!sigitFiles.length) {
return ''
return displayValue
}
return (
@ -589,7 +606,7 @@ export const DrawPDFFields = (props: Props) => {
<ExtensionFileBox extension={file.extension} />
)}
</div>
{i < selectedFiles.length - 1 && <FileDivider />}
{i < sigitFiles.length - 1 && <FileDivider />}
</React.Fragment>
)
})}

View File

@ -84,3 +84,14 @@
padding: 5px 0;
}
}
.counterpartSelectValue {
display: flex;
}
.counterpartAvatar {
img {
width: 21px;
height: 21px;
}
}

View File

@ -5,7 +5,7 @@ import { AvatarIconButton } from '../UserAvatarIconButton'
import { Link } from 'react-router-dom'
import { useProfileMetadata } from '../../hooks/useProfileMetadata'
import { Tooltip } from '@mui/material'
import { shorten } from '../../utils'
import { getProfileUsername } from '../../utils'
import { TooltipChild } from '../TooltipChild'
interface UserAvatarProps {
@ -22,7 +22,7 @@ export const UserAvatar = ({
isNameVisible = false
}: UserAvatarProps) => {
const profile = useProfileMetadata(pubkey)
const name = profile?.display_name || profile?.name || shorten(pubkey)
const name = getProfileUsername(pubkey, profile)
const image = profile?.picture
return (

View File

@ -39,7 +39,8 @@ import {
signEventForMetaFile,
updateUsersAppData,
uploadToFileStorage,
DEFAULT_TOOLBOX
DEFAULT_TOOLBOX,
settleAllFullfilfedPromises
} from '../../utils'
import { Container } from '../../components/Container'
import fileListStyles from '../../components/FileList/style.module.scss'
@ -60,7 +61,8 @@ import {
faTrash,
faUpload
} from '@fortawesome/free-solid-svg-icons'
import { SigitFile } from '../../utils/file.ts'
import { getSigitFile, SigitFile } from '../../utils/file.ts'
import _ from 'lodash'
export const CreatePage = () => {
const navigate = useNavigate()
@ -106,9 +108,31 @@ export const CreatePage = () => {
{}
)
const [drawnFiles, setDrawnFiles] = useState<SigitFile[]>([])
const [parsingPdf, setIsParsing] = useState<boolean>(false)
useEffect(() => {
if (selectedFiles) {
/**
* Reads the binary files and converts to internal file type
* and sets to a state (adds images if it's a PDF)
*/
const parsePages = async () => {
const files = await settleAllFullfilfedPromises(
selectedFiles,
getSigitFile
)
setDrawnFiles(files)
}
setIsParsing(true)
parsePages().finally(() => {
setIsParsing(false)
})
}
}, [selectedFiles])
const [selectedTool, setSelectedTool] = useState<DrawTool>()
const [toolbox] = useState<DrawTool[]>(DEFAULT_TOOLBOX)
/**
* Changes the drawing tool
@ -282,6 +306,19 @@ export const CreatePage = () => {
const handleRemoveUser = (pubkey: string) => {
setUsers((prev) => prev.filter((user) => user.pubkey !== pubkey))
// Set counterpart to ''
const drawnFilesCopy = _.cloneDeep(drawnFiles)
drawnFilesCopy.forEach((s) => {
s.pages?.forEach((p) => {
p.drawnFields.forEach((d) => {
if (d.counterpart === hexToNpub(pubkey)) {
d.counterpart = ''
}
})
})
})
setDrawnFiles(drawnFilesCopy)
}
/**
@ -724,10 +761,6 @@ export const CreatePage = () => {
}
}
const onDrawFieldsChange = (sigitFiles: SigitFile[]) => {
setDrawnFiles(sigitFiles)
}
if (authUrl) {
return (
<iframe
@ -841,20 +874,27 @@ export const CreatePage = () => {
</div>
<div className={`${styles.paperGroup} ${styles.toolbox}`}>
{toolbox.map((drawTool: DrawTool, index: number) => {
{DEFAULT_TOOLBOX.filter((drawTool) => !drawTool.isHidden).map(
(drawTool: DrawTool, index: number) => {
return (
<div
key={index}
{...(drawTool.active && {
{...(!drawTool.isComingSoon && {
onClick: () => handleToolSelect(drawTool)
})}
className={`${styles.toolItem} ${selectedTool?.identifier === drawTool.identifier ? styles.selected : ''} ${!drawTool.active ? styles.comingSoon : ''}
className={`${styles.toolItem} ${selectedTool?.identifier === drawTool.identifier ? styles.selected : ''} ${drawTool.isComingSoon ? styles.comingSoon : ''}
`}
>
<FontAwesomeIcon fontSize={'15px'} icon={drawTool.icon} />
<FontAwesomeIcon
fontSize={'15px'}
icon={drawTool.icon}
/>
{drawTool.label}
{drawTool.active ? (
<FontAwesomeIcon fontSize={'15px'} icon={faEllipsis} />
{!drawTool.isComingSoon ? (
<FontAwesomeIcon
fontSize={'15px'}
icon={faEllipsis}
/>
) : (
<span className={styles.comingSoonPlaceholder}>
Coming soon
@ -862,7 +902,8 @@ export const CreatePage = () => {
)}
</div>
)
})}
}
)}
</div>
<Button onClick={handleCreate} variant="contained">
@ -878,13 +919,17 @@ export const CreatePage = () => {
centerIcon={faFile}
rightIcon={faToolbox}
>
{parsingPdf ? (
<LoadingSpinner variant="small" />
) : (
<DrawPDFFields
metadata={metadata}
users={users}
selectedFiles={selectedFiles}
onDrawFieldsChange={onDrawFieldsChange}
metadata={metadata}
selectedTool={selectedTool}
sigitFiles={drawnFiles}
setSigitFiles={setDrawnFiles}
/>
)}
</StickySideColumns>
</Container>
</>

View File

@ -1,7 +1,6 @@
import ContentCopyIcon from '@mui/icons-material/ContentCopy'
import EditIcon from '@mui/icons-material/Edit'
import { Box, IconButton, SxProps, Theme, Typography } from '@mui/material'
import { truncate } from 'lodash'
import { Event, VerifiedEvent, kinds, nip19 } from 'nostr-tools'
import { useEffect, useMemo, useState } from 'react'
import { useSelector } from 'react-redux'
@ -14,6 +13,7 @@ import { State } from '../../store/rootReducer'
import { NostrJoiningBlock, ProfileMetadata } from '../../types'
import {
getNostrJoiningBlockNumber,
getProfileUsername,
getRoboHashPicture,
hexToNpub,
shorten
@ -42,15 +42,7 @@ export const ProfilePage = () => {
const [isLoading, setIsLoading] = useState(true)
const [loadingSpinnerDesc] = useState('Fetching metadata')
const profileName =
pubkey &&
profileMetadata &&
truncate(
profileMetadata.display_name || profileMetadata.name || hexToNpub(pubkey),
{
length: 16
}
)
const profileName = pubkey && getProfileUsername(pubkey, profileMetadata)
useEffect(() => {
if (npub) {

View File

@ -31,7 +31,10 @@ export interface DrawTool {
icon: IconDefinition
defaultValue?: string
selected?: boolean
active?: boolean
/** show or hide the toolbox item */
isHidden?: boolean
/** show or hide "coming soon" message on the toolbox item */
isComingSoon?: boolean
}
export enum MarkType {

View File

@ -1,6 +1,7 @@
export interface ProfileMetadata {
name?: string
display_name?: string
/** @deprecated use name instead */
username?: string
picture?: string
banner?: string

View File

@ -3,26 +3,26 @@ import { hexToNpub } from './nostr.ts'
import { Meta, SignedEventContent } from '../types'
import { Event } from 'nostr-tools'
import { EMPTY } from './const.ts'
import { MarkType } from '../types/drawing.ts'
import { DrawTool, MarkType } from '../types/drawing.ts'
import {
faT,
faSignature,
faBriefcase,
faIdCard,
faHeading,
faClock,
faCalendarDays,
fa1,
faImage,
faSquareCheck,
faCalendarDays,
faCheckDouble,
faPaperclip,
faCircleDot,
faSquareCaretDown,
faTableCellsLarge,
faStamp,
faCreditCard,
faPhone
faHeading,
faImage,
faPaperclip,
faPhone,
faSquareCaretDown,
faSquareCheck,
faStamp,
faTableCellsLarge
} from '@fortawesome/free-solid-svg-icons'
/**
@ -152,114 +152,113 @@ const findOtherUserMarks = (marks: Mark[], pubkey: string): Mark[] => {
return marks.filter((mark) => mark.npub !== hexToNpub(pubkey))
}
export const DEFAULT_TOOLBOX = [
export const DEFAULT_TOOLBOX: DrawTool[] = [
{
identifier: MarkType.TEXT,
icon: faT,
label: 'Text',
active: true
},
{
identifier: MarkType.SIGNATURE,
icon: faSignature,
label: 'Signature',
active: false
identifier: MarkType.FULLNAME,
icon: faIdCard,
label: 'Full Name',
isComingSoon: true
},
{
identifier: MarkType.JOBTITLE,
icon: faBriefcase,
label: 'Job Title',
active: false
isComingSoon: true
},
{
identifier: MarkType.FULLNAME,
icon: faIdCard,
label: 'Full Name',
active: false
},
{
identifier: MarkType.INITIALS,
icon: faHeading,
label: 'Initials',
active: false
identifier: MarkType.SIGNATURE,
icon: faSignature,
label: 'Signature',
isComingSoon: true
},
{
identifier: MarkType.DATETIME,
icon: faClock,
label: 'Date Time',
active: false
isComingSoon: true
},
{

Can we rely on the active flag rather than commenting out?

Can we rely on the `active` flag rather than commenting out?
Outdated
Review

The active flag is for coming soon currently, and instead of adding a new flag to differentiate between coming soon and visually hidden, I opted to comment out.

The active flag is for `coming soon` currently, and instead of adding a new flag to differentiate between coming soon and visually hidden, I opted to comment out.

Can you please add a comment to capture the difference in the codebase?

Can you please add a comment to capture the difference in the codebase?
Outdated
Review

Okay, made it clearer with new property names, isHidden and isComingSoon so there is no confusion as with active (also removed comments and used isHidden instead).

Okay, made it clearer with new property names, `isHidden` and `isComingSoon` so there is no confusion as with `active` (also removed comments and used `isHidden` instead).
identifier: MarkType.DATE,
icon: faCalendarDays,
label: 'Date',
active: false
identifier: MarkType.TEXT,
icon: faT,
label: 'Text'
},
{
identifier: MarkType.NUMBER,
icon: fa1,
label: 'Number',
active: false
isComingSoon: true
},
{
identifier: MarkType.INITIALS,
icon: faHeading,
label: 'Initials',
isHidden: true
},
{
identifier: MarkType.DATE,
icon: faCalendarDays,
label: 'Date',
isHidden: true
},
{
identifier: MarkType.IMAGES,
icon: faImage,
label: 'Images',
active: false
isHidden: true
},
{
identifier: MarkType.CHECKBOX,
icon: faSquareCheck,
label: 'Checkbox',
active: false
isHidden: true
},
{
identifier: MarkType.MULTIPLE,
icon: faCheckDouble,
label: 'Multiple',
active: false
isHidden: true
},
{
identifier: MarkType.FILE,
icon: faPaperclip,
label: 'File',
active: false
isHidden: true
},
{
identifier: MarkType.RADIO,
icon: faCircleDot,
label: 'Radio',
active: false
isHidden: true
},
{
identifier: MarkType.SELECT,
icon: faSquareCaretDown,
label: 'Select',
active: false
isHidden: true
},
{
identifier: MarkType.CELLS,
icon: faTableCellsLarge,
label: 'Cells',
active: false
isHidden: true
},
{
identifier: MarkType.STAMP,
icon: faStamp,
label: 'Stamp',
active: false
isHidden: true
},
{
identifier: MarkType.PAYMENT,
icon: faCreditCard,
label: 'Payment',
active: false
isHidden: true
},
{
identifier: MarkType.PHONE,
icon: faPhone,
label: 'Phone',
active: false
isHidden: true
}
]

View File

@ -1,6 +1,6 @@
import { bytesToHex, hexToBytes } from '@noble/hashes/utils'
import axios from 'axios'
import _ from 'lodash'
import _, { truncate } from 'lodash'
import {
Event,
EventTemplate,
@ -30,7 +30,7 @@ import {
import { AuthState, Keys } from '../store/auth/types'
import { RelaysState } from '../store/relays/types'
import store from '../store/store'
import { Meta, SignedEvent, UserAppData } from '../types'
import { Meta, ProfileMetadata, SignedEvent, UserAppData } from '../types'
import { getDefaultRelayMap } from './relays'
import { parseJson, removeLeadingSlash } from './string'
import { timeout } from './utils'
@ -974,3 +974,16 @@ export const sendNotification = async (receiver: string, meta: Meta) => {
throw err
})
}
/**
* Show user's name, first available in order: display_name, name, or npub as fallback
* @param npub User identifier, it can be either pubkey or npub1 (we only show npub)
* @param profile User profile
*/
export const getProfileUsername = (
npub: `npub1${string}` | string,
profile?: ProfileMetadata
) =>
truncate(profile?.display_name || profile?.name || hexToNpub(npub), {
length: 16
})