feat: add UserAvatar, UserIconButton

Closes #68 - Only use getRoboHashPicture function (set 1) for Avatars
This commit is contained in:
enes 2024-07-31 13:53:36 +02:00
parent 5d415a2359
commit 20bb05ddc6
10 changed files with 125 additions and 91 deletions

View File

@ -0,0 +1,42 @@
import { useNavigate } from 'react-router-dom'
import { getProfileRoute } from '../../routes'
import styles from './styles.module.scss'
import React from 'react'
import { AvatarIconButton } from '../UserAvatarIconButton'
interface UserAvatarProps {
name: string
pubkey: string
image?: string
}
/**
* This component will be used for the displaying username and profile picture.
* Clicking will navigate to the user's profile.
*/
export const UserAvatar = ({ pubkey, name, image }: UserAvatarProps) => {
const navigate = useNavigate()
const handleClick = (e: React.MouseEvent<HTMLElement, MouseEvent>) => {
e.stopPropagation()
navigate(getProfileRoute(pubkey))
}
return (
<div className={styles.container}>
<AvatarIconButton
src={image}
hexKey={pubkey}
aria-label={`account of user ${name}`}
color="inherit"
onClick={handleClick}
/>
{name ? (
<label onClick={handleClick} className={styles.username}>
{name}
</label>
) : null}
</div>
)
}

View File

@ -0,0 +1,13 @@
.container {
display: flex;
align-items: center;
gap: 10px;
flex-grow: 1;
}
.username {
cursor: pointer;
font-weight: 500;
font-size: 14px;
color: var(--text-color);
}

View File

@ -0,0 +1,32 @@
import { IconButton, IconButtonProps } from '@mui/material'
import styles from './style.module.scss'
import { getRoboHashPicture } from '../../utils'
interface AvatarIconButtonProps extends IconButtonProps {
src: string | undefined
hexKey: string | undefined
}
/**
* This component displays profile image inside IconButton
* @param {string | undefined} props.src - image source or robohash picture
* @param {string | undefined} props.hexKey - robohash and affects border
* @param {IconButtonProps} props - component extends mui's IconButton
*/
export const AvatarIconButton = (props: AvatarIconButtonProps) => {
const { src, hexKey, ...rest } = props
return (
<IconButton {...rest}>
<img
src={src || getRoboHashPicture(hexKey)}
alt="user image"
className={`${styles.icon}`}
style={{
borderStyle: hexKey ? 'solid' : 'none',
borderColor: `#${hexKey?.substring(0, 6)}`
}}
/>
</IconButton>
)
}

View File

@ -0,0 +1,7 @@
.icon {
width: 40px;
height: 40px;
border-radius: 50%;
border-width: 3px;
overflow: hidden;
}

View File

@ -0,0 +1,7 @@
.container {
display: flex;
flex-direction: row;
justify-content: end;
align-items: center;
gap: 12px;
}

View File

@ -1,9 +1,9 @@
import { Box, IconButton, Typography, useTheme } from '@mui/material' import { Typography } from '@mui/material'
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
import { useNavigate } from 'react-router-dom'
import { getProfileRoute } from '../routes'
import { State } from '../store/rootReducer' import { State } from '../store/rootReducer'
import { hexToNpub } from '../utils'
import styles from './username.module.scss'
import { AvatarIconButton } from './UserAvatarIconButton'
type Props = { type Props = {
username: string username: string
@ -11,19 +11,15 @@ type Props = {
handleClick: (event: React.MouseEvent<HTMLElement>) => void handleClick: (event: React.MouseEvent<HTMLElement>) => void
} }
/**
* This component will be used for the displaying logged in user in AppBar.
* Clicking will open the menu.
*/
const Username = ({ username, avatarContent, handleClick }: Props) => { const Username = ({ username, avatarContent, handleClick }: Props) => {
const hexKey = useSelector((state: State) => state.auth.usersPubkey) const hexKey = useSelector((state: State) => state.auth.usersPubkey)
return ( return (
<div <div className={styles.container}>
style={{
display: 'flex',
flexDirection: 'row',
justifyContent: 'end',
alignItems: 'center',
gap: '12px'
}}
>
<Typography <Typography
variant="h6" variant="h6"
sx={{ sx={{
@ -35,79 +31,16 @@ const Username = ({ username, avatarContent, handleClick }: Props) => {
> >
{username} {username}
</Typography> </Typography>
<IconButton <AvatarIconButton
src={avatarContent}
hexKey={hexKey}
aria-label="account of current user" aria-label="account of current user"
aria-controls="menu-appbar" aria-controls="menu-appbar"
aria-haspopup="true" aria-haspopup="true"
onClick={handleClick} onClick={handleClick}
color="inherit"
>
<img
src={avatarContent}
alt="user-avatar"
className="profile-image"
style={{
borderWidth: '3px',
borderStyle: hexKey ? 'solid' : 'none',
borderColor: `#${hexKey?.substring(0, 6)}`
}}
/> />
</IconButton>
</div> </div>
) )
} }
export default Username export default Username
type UserProps = {
pubkey: string
name: string
image?: string
}
/**
* This component will be used for the displaying username and profile picture.
* If image is not available, robohash image will be displayed
*/
export const UserComponent = ({ pubkey, name, image }: UserProps) => {
const theme = useTheme()
const navigate = useNavigate()
const npub = hexToNpub(pubkey)
const roboImage = `https://robohash.org/${npub}.png?set=set3`
const handleClick = (e: React.MouseEvent<HTMLElement, MouseEvent>) => {
e.stopPropagation()
// navigate to user's profile
navigate(getProfileRoute(pubkey))
}
return (
<Box
sx={{ display: 'flex', alignItems: 'center', gap: '10px', flexGrow: 1 }}
>
<img
src={image || roboImage}
alt="User Image"
className="profile-image"
style={{
borderWidth: '3px',
borderStyle: 'solid',
borderColor: `#${pubkey.substring(0, 6)}`
}}
onClick={handleClick}
/>
<Typography
component="label"
sx={{
textAlign: 'center',
cursor: 'pointer',
color: theme.palette.text.primary
}}
onClick={handleClick}
>
{name}
</Typography>
</Box>
)
}

View File

@ -30,7 +30,7 @@ import { useSelector } from 'react-redux'
import { useLocation, useNavigate } from 'react-router-dom' import { useLocation, useNavigate } from 'react-router-dom'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
import { LoadingSpinner } from '../../components/LoadingSpinner' import { LoadingSpinner } from '../../components/LoadingSpinner'
import { UserComponent } from '../../components/username' import { UserAvatar } from '../../components/UserAvatar'
import { MetadataController, NostrController } from '../../controllers' import { MetadataController, NostrController } from '../../controllers'
import { appPrivateRoutes } from '../../routes' import { appPrivateRoutes } from '../../routes'
import { State } from '../../store/rootReducer' import { State } from '../../store/rootReducer'
@ -799,7 +799,7 @@ const DisplayUser = ({
return ( return (
<TableRow key={index}> <TableRow key={index}>
<TableCell className={styles.tableCell}> <TableCell className={styles.tableCell}>
<UserComponent <UserAvatar
pubkey={user.pubkey} pubkey={user.pubkey}
name={ name={
userMeta?.display_name || userMeta?.display_name ||
@ -954,7 +954,7 @@ const SignerRow = ({
sx={{ display: 'flex', alignItems: 'center', gap: '10px' }} sx={{ display: 'flex', alignItems: 'center', gap: '10px' }}
> >
<DragHandle /> <DragHandle />
<UserComponent <UserAvatar
pubkey={user.pubkey} pubkey={user.pubkey}
name={ name={
userMeta?.display_name || userMeta?.display_name ||

View File

@ -5,7 +5,7 @@ import { Event, kinds, verifyEvent } from 'nostr-tools'
import { Dispatch, SetStateAction, useEffect, useRef, useState } from 'react' import { Dispatch, SetStateAction, useEffect, useRef, useState } from 'react'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
import { UserComponent } from '../../components/username' import { UserAvatar } from '../../components/UserAvatar'
import { MetadataController } from '../../controllers' import { MetadataController } from '../../controllers'
import { useAppSelector } from '../../hooks' import { useAppSelector } from '../../hooks'
import { appPrivateRoutes, appPublicRoutes } from '../../routes' import { appPrivateRoutes, appPublicRoutes } from '../../routes'
@ -290,7 +290,7 @@ const DisplaySigit = ({ meta, profiles, setProfiles }: SigitProps) => {
(function () { (function () {
const profile = profiles[submittedBy] const profile = profiles[submittedBy]
return ( return (
<UserComponent <UserAvatar
pubkey={submittedBy} pubkey={submittedBy}
name={ name={
profile?.display_name || profile?.display_name ||
@ -370,7 +370,7 @@ const DisplaySigner = ({ meta, profile, pubkey }: DisplaySignerProps) => {
<Typography variant="button" className={styles.status}> <Typography variant="button" className={styles.status}>
{signStatus} {signStatus}
</Typography> </Typography>
<UserComponent <UserAvatar
pubkey={pubkey} pubkey={pubkey}
name={ name={
profile?.display_name || profile?.display_name ||

View File

@ -30,7 +30,7 @@ import saveAs from 'file-saver'
import { kinds, Event } from 'nostr-tools' import { kinds, Event } from 'nostr-tools'
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
import { UserComponent } from '../../../components/username' import { UserAvatar } from '../../../components/UserAvatar'
import { MetadataController } from '../../../controllers' import { MetadataController } from '../../../controllers'
import { npubToHex, shorten, hexToNpub, parseJson } from '../../../utils' import { npubToHex, shorten, hexToNpub, parseJson } from '../../../utils'
import styles from '../style.module.scss' import styles from '../style.module.scss'
@ -172,7 +172,7 @@ export const DisplayMeta = ({
{(function () { {(function () {
const profile = metadata[submittedBy] const profile = metadata[submittedBy]
return ( return (
<UserComponent <UserAvatar
pubkey={submittedBy} pubkey={submittedBy}
name={ name={
profile?.display_name || profile?.display_name ||
@ -372,7 +372,7 @@ const DisplayUser = ({
return ( return (
<TableRow> <TableRow>
<TableCell className={styles.tableCell}> <TableCell className={styles.tableCell}>
<UserComponent <UserAvatar
pubkey={user.pubkey} pubkey={user.pubkey}
name={ name={
userMeta?.display_name || userMeta?.display_name ||

View File

@ -14,7 +14,7 @@ import { Event, kinds, verifyEvent } from 'nostr-tools'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
import { LoadingSpinner } from '../../components/LoadingSpinner' import { LoadingSpinner } from '../../components/LoadingSpinner'
import { UserComponent } from '../../components/username' import { UserAvatar } from '../../components/UserAvatar'
import { MetadataController } from '../../controllers' import { MetadataController } from '../../controllers'
import { import {
CreateSignatureEventContent, CreateSignatureEventContent,
@ -403,7 +403,7 @@ export const VerifyPage = () => {
return ( return (
<> <>
<UserComponent <UserAvatar
pubkey={pubkey} pubkey={pubkey}
name={ name={
profile?.display_name || profile?.name || shorten(hexToNpub(pubkey)) profile?.display_name || profile?.name || shorten(hexToNpub(pubkey))