feat: stop using smiley default avatar, use first letter of name on colored square as fallback

This commit is contained in:
Paul Bienkowski 2021-05-07 14:57:50 +02:00
parent 76620c5e8f
commit 34042ede54
9 changed files with 96 additions and 10 deletions
api
src/models
views
frontend/src

View file

@ -71,7 +71,7 @@ class User extends mongoose.Model {
email: this.email, email: this.email,
token: this.generateJWT(), token: this.generateJWT(),
bio: this.bio, bio: this.bio,
image: this.image || 'https://static.productionready.io/images/smiley-cyrus.jpg', image: this.image,
areTracksVisibleForAll: this.areTracksVisibleForAll, areTracksVisibleForAll: this.areTracksVisibleForAll,
apiKey: this._id, apiKey: this._id,
}; };
@ -81,7 +81,7 @@ class User extends mongoose.Model {
return { return {
username: this.username, username: this.username,
bio: this.bio, bio: this.bio,
image: this.image || 'https://static.productionready.io/images/smiley-cyrus.jpg', image: this.image,
}; };
} }
} }

View file

@ -1,8 +1,10 @@
html html
head head
base(href=baseUrl) base(href=baseUrl)
title block title title
block title
| Authorization Server | Authorization Server
| - OpenBikeSensor Account
link(rel="stylesheet", href="/semantic-ui/semantic.min.css") link(rel="stylesheet", href="/semantic-ui/semantic.min.css")

View file

@ -16,7 +16,7 @@ import {
TracksPage, TracksPage,
UploadPage, UploadPage,
} from 'pages' } from 'pages'
import {LoginButton} from 'components' import {Avatar, LoginButton} from 'components'
const App = connect((state) => ({login: state.login}))(function App({login}) { const App = connect((state) => ({login: state.login}))(function App({login}) {
return ( return (
@ -54,7 +54,7 @@ const App = connect((state) => ({login: state.login}))(function App({login}) {
<> <>
<li> <li>
<Link to="/settings"> <Link to="/settings">
<Image src={login.image} avatar /> <Avatar user={login} />
</Link> </Link>
</li> </li>
<li> <li>

View file

@ -0,0 +1,38 @@
import React from 'react'
import {Comment} from 'semantic-ui-react'
import './styles.scss'
function hashCode(s) {
let hash = 0
for (let i = 0; i < s.length; i++) {
hash = (hash << 5) - hash + s.charCodeAt(i)
hash |= 0
}
return hash
}
function getColor(s) {
const h = Math.floor(hashCode(s)) % 360
return `hsl(${h}, 50%, 50%)`
}
export default function Avatar({user}) {
const {image, username} = user || {}
if (image) {
return <Comment.Avatar src={image} />
}
if (!username) {
return <div className="avatar empty-avatar" />
}
const color = getColor(username)
return (
<div className="avatar text-avatar" style={{background: color}}>
{username && <span>{username[0]}</span>}
</div>
)
}

View file

@ -0,0 +1,43 @@
.avatar.text-avatar {
display: inline-flex;
align-items: center;
justify-content: center;
vertical-align: -3px;
> span {
color: white;
font-size: 20pt;
text-transform: uppercase;
display: inline;
a:hover & {
text-decoration: none !important;
}
}
}
.avatar.empty-avatar {
background: #AAA;
}
.avatar.text-avatar,
.avatar.empty-avatar {
// border-radius: 0.25rem;
border-radius: 10%;
width: 2.5em;
height: 2.5em;
.ui.comments & {
display: inline-flex;
width: 2.5em;
height: 2.5em;
}
.tiny.image & {
width: 64px;
height: 64px;
}
}

View file

@ -1,3 +1,4 @@
export {default as Avatar} from './Avatar'
export {default as FileDrop} from './FileDrop' export {default as FileDrop} from './FileDrop'
export {default as FileUploadField} from './FileUploadField' export {default as FileUploadField} from './FileUploadField'
export {default as FormattedDate} from './FormattedDate' export {default as FormattedDate} from './FormattedDate'

View file

@ -2,7 +2,7 @@ import React from 'react'
import {Message, Segment, Form, Button, Loader, Header, Comment} from 'semantic-ui-react' import {Message, Segment, Form, Button, Loader, Header, Comment} from 'semantic-ui-react'
import Markdown from 'react-markdown' import Markdown from 'react-markdown'
import {FormattedDate} from 'components' import {Avatar, FormattedDate} from 'components'
function CommentForm({onSubmit}) { function CommentForm({onSubmit}) {
const [body, setBody] = React.useState('') const [body, setBody] = React.useState('')
@ -32,7 +32,7 @@ export default function TrackComments({comments, onSubmit, onDelete, login, hide
{comments?.map((comment: TrackComment) => ( {comments?.map((comment: TrackComment) => (
<Comment key={comment.id}> <Comment key={comment.id}>
<Comment.Avatar src={comment.author.image} /> <Avatar user={comment.author} />
<Comment.Content> <Comment.Content>
<Comment.Author as="a">{comment.author.username}</Comment.Author> <Comment.Author as="a">{comment.author.username}</Comment.Author>
<Comment.Metadata> <Comment.Metadata>

View file

@ -8,7 +8,7 @@ import {map, switchMap, distinctUntilChanged} from 'rxjs/operators'
import _ from 'lodash' import _ from 'lodash'
import type {Track} from 'types' import type {Track} from 'types'
import {Page, StripMarkdown} from 'components' import {Avatar, Page, StripMarkdown} from 'components'
import api from 'api' import api from 'api'
import {useQueryParam} from 'query' import {useQueryParam} from 'query'
@ -99,7 +99,9 @@ function maxLength(t, max) {
export function TrackListItem({track, privateTracks = false}) { export function TrackListItem({track, privateTracks = false}) {
return ( return (
<Item key={track.slug}> <Item key={track.slug}>
<Item.Image size="tiny" src={track.author.image} /> <Item.Image size="tiny">
<Avatar user={track.author} />
</Item.Image>
<Item.Content> <Item.Content>
<Item.Header as={Link} to={`/tracks/${track.slug}`}> <Item.Header as={Link} to={`/tracks/${track.slug}`}>
{track.title || 'Unnamed track'} {track.title || 'Unnamed track'}

View file

@ -2,7 +2,7 @@ import type {FeatureCollection, Point} from 'geojson'
export type UserProfile = { export type UserProfile = {
username: string username: string
image: string image?: string | null
bio?: string | null bio?: string | null
} }