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 { useNavigate } from 'react-router-dom'
import { getProfileRoute } from '../routes'
import { State } from '../store/rootReducer'
import { hexToNpub } from '../utils'
import styles from './username.module.scss'
import { AvatarIconButton } from './UserAvatarIconButton'
type Props = {
username: string
@ -11,19 +11,15 @@ type Props = {
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 hexKey = useSelector((state: State) => state.auth.usersPubkey)
return (
<div
style={{
display: 'flex',
flexDirection: 'row',
justifyContent: 'end',
alignItems: 'center',
gap: '12px'
}}
>
<div className={styles.container}>
<Typography
variant="h6"
sx={{
@ -35,79 +31,16 @@ const Username = ({ username, avatarContent, handleClick }: Props) => {
>
{username}
</Typography>
<IconButton
<AvatarIconButton
src={avatarContent}
hexKey={hexKey}
aria-label="account of current user"
aria-controls="menu-appbar"
aria-haspopup="true"
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>
)
}
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 { toast } from 'react-toastify'
import { LoadingSpinner } from '../../components/LoadingSpinner'
import { UserComponent } from '../../components/username'
import { UserAvatar } from '../../components/UserAvatar'
import { MetadataController, NostrController } from '../../controllers'
import { appPrivateRoutes } from '../../routes'
import { State } from '../../store/rootReducer'
@ -799,7 +799,7 @@ const DisplayUser = ({
return (
<TableRow key={index}>
<TableCell className={styles.tableCell}>
<UserComponent
<UserAvatar
pubkey={user.pubkey}
name={
userMeta?.display_name ||
@ -954,7 +954,7 @@ const SignerRow = ({
sx={{ display: 'flex', alignItems: 'center', gap: '10px' }}
>
<DragHandle />
<UserComponent
<UserAvatar
pubkey={user.pubkey}
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 { useNavigate } from 'react-router-dom'
import { toast } from 'react-toastify'
import { UserComponent } from '../../components/username'
import { UserAvatar } from '../../components/UserAvatar'
import { MetadataController } from '../../controllers'
import { useAppSelector } from '../../hooks'
import { appPrivateRoutes, appPublicRoutes } from '../../routes'
@ -290,7 +290,7 @@ const DisplaySigit = ({ meta, profiles, setProfiles }: SigitProps) => {
(function () {
const profile = profiles[submittedBy]
return (
<UserComponent
<UserAvatar
pubkey={submittedBy}
name={
profile?.display_name ||
@ -370,7 +370,7 @@ const DisplaySigner = ({ meta, profile, pubkey }: DisplaySignerProps) => {
<Typography variant="button" className={styles.status}>
{signStatus}
</Typography>
<UserComponent
<UserAvatar
pubkey={pubkey}
name={
profile?.display_name ||

View File

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

View File

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