feat: stop using smiley default avatar, use first letter of name on colored square as fallback
This commit is contained in:
parent
76620c5e8f
commit
34042ede54
9 changed files with 96 additions and 10 deletions
|
@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
38
frontend/src/components/Avatar/index.tsx
Normal file
38
frontend/src/components/Avatar/index.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
43
frontend/src/components/Avatar/styles.scss
Normal file
43
frontend/src/components/Avatar/styles.scss
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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'}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue