Update Userpic component (#160)

This commit is contained in:
Ilya Y 2023-08-11 19:42:41 +03:00 committed by GitHub
parent c95907968c
commit f9c30a99cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 137 additions and 139 deletions

View File

@ -4,7 +4,7 @@ import { getPagePath } from '@nanostores/router'
import MD from './MD' import MD from './MD'
import { AuthorCard } from '../Author/AuthorCard' import { AuthorCard } from '../Author/AuthorCard'
import Userpic from '../Author/Userpic' import { Userpic } from '../Author/Userpic'
import { CommentRatingControl } from './CommentRatingControl' import { CommentRatingControl } from './CommentRatingControl'
import { CommentDate } from './CommentDate' import { CommentDate } from './CommentDate'
import { ShowIfAuthenticated } from '../_shared/ShowIfAuthenticated' import { ShowIfAuthenticated } from '../_shared/ShowIfAuthenticated'
@ -123,7 +123,8 @@ export const Comment = (props: Props) => {
fallback={ fallback={
<div> <div>
<Userpic <Userpic
user={comment().createdBy as Author} name={comment().createdBy.name}
userpic={comment().createdBy.userpic}
isBig={false} isBig={false}
class={clsx({ class={clsx({
[styles.compactUserpic]: props.compact [styles.compactUserpic]: props.compact

View File

@ -1,5 +1,5 @@
import type { Author } from '../../graphql/types.gen' import type { Author } from '../../graphql/types.gen'
import Userpic from './Userpic' import { Userpic } from './Userpic'
import { Icon } from '../_shared/Icon' import { Icon } from '../_shared/Icon'
import styles from './AuthorCard.module.scss' import styles from './AuthorCard.module.scss'
import { createMemo, createSignal, For, Show } from 'solid-js' import { createMemo, createSignal, For, Show } from 'solid-js'
@ -101,7 +101,8 @@ export const AuthorCard = (props: AuthorCardProps) => {
}} }}
> >
<Userpic <Userpic
user={props.author} name={props.author.name}
userpic={props.author.userpic}
hasLink={props.hasLink} hasLink={props.hasLink}
isBig={props.isAuthorPage} isBig={props.isAuthorPage}
isAuthorsList={props.isAuthorsList} isAuthorsList={props.isAuthorsList}

View File

@ -1,65 +0,0 @@
import { Show } from 'solid-js'
import type { Author, User } from '../../graphql/types.gen'
import styles from './Userpic.module.scss'
import { clsx } from 'clsx'
interface UserpicProps {
user: Author | User
hasLink?: boolean
isBig?: boolean
class?: string
isAuthorsList?: boolean
isFeedMode?: boolean
}
export default (props: UserpicProps) => {
const letters = () => {
const names = props.user && props.user.name ? props.user.name.split(' ') : []
return names[0][0] + (names.length > 1 ? names[1][0] : '')
}
return (
<div
class={clsx(styles.circlewrap, props.class)}
classList={{
[styles.big]: props.isBig,
[styles.authorsList]: props.isAuthorsList,
[styles.feedMode]: props.isFeedMode
}}
>
<Show when={props.hasLink}>
<a href={`/author/${props.user.slug}`}>
<Show
when={props.user && props.user.userpic === ''}
fallback={
<img
src={props.user.userpic || '/icons/user-default.svg'}
alt={props.user.name || ''}
classList={{ [styles.anonymous]: !props.user.userpic }}
/>
}
>
<div class={styles.userpic}>{letters()}</div>
</Show>
</a>
</Show>
<Show when={!props.hasLink}>
<Show
when={props.user && props.user.userpic === ''}
fallback={
<img
src={props.user.userpic || '/icons/user-default.svg'}
alt={props.user.name || ''}
classList={{ [styles.anonymous]: !props.user.userpic }}
loading="lazy"
/>
}
>
<div class={styles.userpic}>{letters()}</div>
</Show>
</Show>
</div>
)
}

View File

@ -1,4 +1,4 @@
.circlewrap { .Userpic {
align-items: baseline; align-items: baseline;
background: #f7f7f8; background: #f7f7f8;
border-radius: 100%; border-radius: 100%;
@ -19,7 +19,7 @@
display: block; display: block;
} }
.userpic { .letters {
background-color: white; background-color: white;
border-radius: 50%; border-radius: 50%;
border: 1.5px solid black; border: 1.5px solid black;
@ -53,24 +53,24 @@
color: #000; color: #000;
} }
} }
}
.big.circlewrap { &.big {
margin-right: 0; margin-right: 0;
max-width: 168px; max-width: 168px;
min-width: 168px; min-width: 168px;
height: 168px; height: 168px;
width: 168px; width: 168px;
@include media-breakpoint-up(md) { @include media-breakpoint-up(md) {
margin-right: 4.8rem; margin-right: 4.8rem;
} }
.userpic { .letters {
font-size: 2em; font-size: 2em;
line-height: 168px; line-height: 168px;
max-width: 100%; max-width: 100%;
width: 100%; width: 100%;
}
} }
} }
@ -81,16 +81,20 @@
height: 6.8rem; height: 6.8rem;
width: 6.8rem; width: 6.8rem;
.userpic { .letters {
line-height: 6.4rem; line-height: 6.4rem;
} }
} }
.feedMode { .feedMode {
.userpic { .letters {
font-size: 0.8rem; font-size: 0.8rem;
line-height: 14px; line-height: 14px;
min-width: 16px; min-width: 16px;
max-width: 16px; max-width: 16px;
} }
} }
.cursorPointer {
cursor: pointer;
}

View File

@ -0,0 +1,62 @@
import { Show } from 'solid-js'
import type { Author, User } from '../../../graphql/types.gen'
import styles from './Userpic.module.scss'
import { clsx } from 'clsx'
import { imageProxy } from '../../../utils/imageProxy'
import { ConditionalWrapper } from '../../_shared/ConditionalWrapper'
import { Loading } from '../../_shared/Loading'
type Props = {
name: string
userpic: string
class?: string
slug?: string
onClick?: () => void
loading?: boolean
isBig?: boolean
hasLink?: boolean
isAuthorsList?: boolean
isFeedMode?: boolean
}
export const Userpic = (props: Props) => {
const letters = () => {
if (!props.name) return
const names = props.name ? props.name.split(' ') : []
return names[0][0] + (names.length > 1 ? names[1][0] : '')
}
return (
<div
class={clsx(styles.Userpic, props.class, {
[styles.big]: props.isBig,
[styles.authorsList]: props.isAuthorsList,
[styles.feedMode]: props.isFeedMode,
[styles.cursorPointer]: props.onClick
})}
onClick={props.onClick}
>
<Show when={!props.loading} fallback={<Loading />}>
<ConditionalWrapper
condition={props.hasLink}
wrapper={(children) => <a href={`/author/${props.slug}`}>{children}</a>}
>
<Show
when={!props.userpic}
fallback={
<img
class={clsx({ [styles.anonymous]: !props.userpic })}
src={imageProxy(props.userpic) || '/icons/user-default.svg'}
alt={props.name || ''}
loading="lazy"
/>
}
>
<div class={styles.letters}>{letters()}</div>
</Show>
</ConditionalWrapper>
</Show>
</div>
)
}

View File

@ -0,0 +1 @@
export { Userpic } from './Userpic'

View File

@ -9,7 +9,7 @@ import { useSession } from '../../../context/session'
import { useLocalize } from '../../../context/localize' import { useLocalize } from '../../../context/localize'
import styles from './Sidebar.module.scss' import styles from './Sidebar.module.scss'
import { clsx } from 'clsx' import { clsx } from 'clsx'
import Userpic from '../../Author/Userpic' import { Userpic } from '../../Author/Userpic'
import { getPagePath } from '@nanostores/router' import { getPagePath } from '@nanostores/router'
import { router, useRouter } from '../../../stores/router' import { router, useRouter } from '../../../stores/router'
@ -138,7 +138,10 @@ export const Sidebar = (props: FeedSidebarProps) => {
> >
<div class={styles.sidebarItemName}> <div class={styles.sidebarItemName}>
<Show when={authorEntities()[authorSlug]}> <Show when={authorEntities()[authorSlug]}>
<Userpic user={authorEntities()[authorSlug]} /> <Userpic
name={authorEntities()[authorSlug].name}
userpic={authorEntities()[authorSlug].userpic}
/>
</Show> </Show>
<Show when={!authorEntities()[authorSlug]}> <Show when={!authorEntities()[authorSlug]}>
<Icon name="hash" class={styles.icon} /> <Icon name="hash" class={styles.icon} />

View File

@ -5,7 +5,7 @@ import { Icon } from '../_shared/Icon'
import { createMemo, createSignal, onCleanup, onMount, Show } from 'solid-js' import { createMemo, createSignal, onCleanup, onMount, Show } from 'solid-js'
import Notifications from './Notifications' import Notifications from './Notifications'
import { ProfilePopup } from './ProfilePopup' import { ProfilePopup } from './ProfilePopup'
import Userpic from '../Author/Userpic' import { Userpic } from '../Author/Userpic'
import { showModal, useWarningsStore } from '../../stores/ui' import { showModal, useWarningsStore } from '../../stores/ui'
import { ShowOnlyOnClient } from '../_shared/ShowOnlyOnClient' import { ShowOnlyOnClient } from '../_shared/ShowOnlyOnClient'
import { useSession } from '../../context/session' import { useSession } from '../../context/session'
@ -216,7 +216,11 @@ export const HeaderAuth = (props: Props) => {
<div class={styles.userControlItem}> <div class={styles.userControlItem}>
<button class={styles.button}> <button class={styles.button}>
<div classList={{ entered: page().path === `/${session().user?.slug}` }}> <div classList={{ entered: page().path === `/${session().user?.slug}` }}>
<Userpic user={session().user} class={styles.userpic} /> <Userpic
name={session().user.name}
userpic={session().user.userpic}
class={styles.userpic}
/>
</div> </div>
</button> </button>
</div> </div>

View File

@ -12,7 +12,7 @@ import { splitToPages } from '../../../utils/splitToPages'
import styles from './Author.module.scss' import styles from './Author.module.scss'
import stylesArticle from '../../Article/Article.module.scss' import stylesArticle from '../../Article/Article.module.scss'
import { clsx } from 'clsx' import { clsx } from 'clsx'
import Userpic from '../../Author/Userpic' import { Userpic } from '../../Author/Userpic'
import { Popup } from '../../_shared/Popup' import { Popup } from '../../_shared/Popup'
import { AuthorCard } from '../../Author/AuthorCard' import { AuthorCard } from '../../Author/AuthorCard'
import { apiClient } from '../../../utils/apiClient' import { apiClient } from '../../../utils/apiClient'
@ -178,12 +178,12 @@ export const AuthorView = (props: AuthorProps) => {
<Switch> <Switch>
<Match when={followers().length <= 3}> <Match when={followers().length <= 3}>
<For each={followers().slice(0, 3)}> <For each={followers().slice(0, 3)}>
{(f) => <Userpic user={f} class={styles.userpic} />} {(f) => <Userpic name={f.name} userpic={f.userpic} class={styles.userpic} />}
</For> </For>
</Match> </Match>
<Match when={followers().length > 3}> <Match when={followers().length > 3}>
<For each={followers().slice(0, 2)}> <For each={followers().slice(0, 2)}>
{(f) => <Userpic user={f} class={styles.userpic} />} {(f) => <Userpic name={f.name} userpic={f.userpic} class={styles.userpic} />}
</For> </For>
<div class={clsx(styles.userpic, styles.subscribersCounter)}> <div class={clsx(styles.userpic, styles.subscribersCounter)}>
{followers().length} {followers().length}

View File

@ -0,0 +1,11 @@
import { JSX } from 'solid-js'
type Props = {
condition: boolean
wrapper: (children: JSX.Element) => JSX.Element
children: JSX.Element
}
export const ConditionalWrapper = (props: Props) => {
return props.condition ? props.wrapper(props.children) : props.children
}

View File

@ -0,0 +1 @@
export { ConditionalWrapper } from './ConditionalWrapper'

View File

@ -1,7 +1,7 @@
import type { Reaction } from '../../../graphql/types.gen'
import { Author, ReactionKind } from '../../../graphql/types.gen'
import { For, Show } from 'solid-js' import { For, Show } from 'solid-js'
import Userpic from '../../Author/Userpic' import type { Reaction } from '../../../graphql/types.gen'
import { ReactionKind } from '../../../graphql/types.gen'
import { Userpic } from '../../Author/Userpic'
import styles from './VotersList.module.scss' import styles from './VotersList.module.scss'
import { clsx } from 'clsx' import { clsx } from 'clsx'
@ -22,7 +22,12 @@ export const VotersList = (props: Props) => {
{(reaction) => ( {(reaction) => (
<li class={styles.item}> <li class={styles.item}>
<div class={styles.user}> <div class={styles.user}>
<Userpic user={reaction.createdBy as Author} isBig={false} isAuthorsList={false} /> <Userpic
name={reaction.createdBy.name}
userpic={reaction.createdBy.userpic}
isBig={false}
isAuthorsList={false}
/>
<a href={`/author/${reaction.createdBy.slug}`}>{reaction.createdBy.name || ''}</a> <a href={`/author/${reaction.createdBy.slug}`}>{reaction.createdBy.name || ''}</a>
</div> </div>
{reaction.kind === ReactionKind.Like ? ( {reaction.kind === ReactionKind.Like ? (

View File

@ -13,34 +13,6 @@ h5 {
margin: 0 0 0.8rem; margin: 0 0 0.8rem;
} }
.avatarContainer {
border-radius: 100%;
overflow: hidden;
position: relative;
height: 18rem;
width: 18rem;
}
.avatar {
height: 100%;
left: 0;
position: absolute;
top: 0;
width: 100%;
cursor: pointer;
background: #ccc;
border: none;
object-fit: cover;
object-position: center;
}
.avatarInput {
border-radius: 100%;
cursor: pointer;
opacity: 0;
z-index: 1;
}
.multipleControls { .multipleControls {
margin-top: 3rem; margin-top: 3rem;
} }

View File

@ -7,13 +7,12 @@ import styles from './Settings.module.scss'
import { useProfileForm } from '../../context/profile' import { useProfileForm } from '../../context/profile'
import { validateUrl } from '../../utils/validateUrl' import { validateUrl } from '../../utils/validateUrl'
import { createFileUploader } from '@solid-primitives/upload' import { createFileUploader } from '@solid-primitives/upload'
import { Loading } from '../../components/_shared/Loading'
import { useSession } from '../../context/session' import { useSession } from '../../context/session'
import { Button } from '../../components/_shared/Button' import { Button } from '../../components/_shared/Button'
import { useSnackbar } from '../../context/snackbar' import { useSnackbar } from '../../context/snackbar'
import { useLocalize } from '../../context/localize' import { useLocalize } from '../../context/localize'
import { Image } from '../../components/_shared/Image'
import { handleFileUpload } from '../../utils/handleFileUpload' import { handleFileUpload } from '../../utils/handleFileUpload'
import { Userpic } from '../../components/Author/Userpic'
export const ProfileSettingsPage = () => { export const ProfileSettingsPage = () => {
const { t } = useLocalize() const { t } = useLocalize()
@ -72,6 +71,8 @@ export const ProfileSettingsPage = () => {
const [hostname, setHostname] = createSignal<string | null>(null) const [hostname, setHostname] = createSignal<string | null>(null)
onMount(() => setHostname(window?.location.host)) onMount(() => setHostname(window?.location.host))
console.log('!!! form:', form)
return ( return (
<PageLayout> <PageLayout>
<Show when={form}> <Show when={form}>
@ -90,16 +91,13 @@ export const ProfileSettingsPage = () => {
<form onSubmit={handleSubmit} enctype="multipart/form-data"> <form onSubmit={handleSubmit} enctype="multipart/form-data">
<h4>{t('Userpic')}</h4> <h4>{t('Userpic')}</h4>
<div class="pretty-form__item"> <div class="pretty-form__item">
<div class={styles.avatarContainer}> <Userpic
<Show when={!isUserpicUpdating()} fallback={<Loading />}> name={form.name}
<Image userpic={form.userpic}
class={styles.avatar} isBig={true}
src={form.userpic} onClick={handleAvatarClick}
alt={form.name} loading={isUserpicUpdating()}
onClick={handleAvatarClick} />
/>
</Show>
</div>
</div> </div>
<h4>{t('Name')}</h4> <h4>{t('Name')}</h4>
<p class="description"> <p class="description">