Feature/profile improvements (#255)

* Show social in profile
* change profile view
* fix facebook
This commit is contained in:
Ilya Y 2023-10-12 17:28:00 +03:00 committed by GitHub
parent c9febc75ea
commit 13b92c2201
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 370 additions and 314 deletions

View File

@ -413,5 +413,7 @@
"video": "video",
"view": "view",
"zine": "zine",
"PublicationsWithCount": "{count, plural, =0 {no publications} one {{count} publication} other {{count} publications}}"
"SubscriptionWithPlurals": "{count, plural, =0 {no subscriptions} one {{count} subscription} other {{count} subscriptions}",
"PublicationsWithCount": "{count, plural, =0 {no publications} one {{count} publication} other {{count} publications}}",
"Edit profile": "Edit profile"
}

View File

@ -430,14 +430,14 @@
"subscriber": "подписчик",
"subscriber_rp": "подписчика",
"subscribers": "подписчиков",
"subscription": "подписка",
"subscription_rp": "подписки",
"subscriptions": "подписок",
"terms of use": "правилами пользования сайтом",
"topics": "темы",
"user already exist": "пользователь уже существует",
"video": "видео",
"view": "просмотр",
"zine": "журнал",
"PublicationsWithCount": "{count, plural, =0 {нет публикаций} one {{count} публикация} few {{count} публикации} other {{count} публикаций}}"
"SubscriberWithCount": "{count, plural, =0 {нет подписчиков} one {{count} подписчик} few {{count} подписчика} other {{count} подписчиков}}",
"SubscriptionWithCount": "{count, plural, =0 {нет подписок} one {{count} подписка} few {{count} подписки} other {{count} подписок}}",
"PublicationsWithCount": "{count, plural, =0 {нет публикаций} one {{count} публикация} few {{count} публикации} other {{count} публикаций}}",
"Edit profile": "Редактировать профиль"
}

View File

@ -124,7 +124,45 @@
flex-wrap: wrap;
}
a {
.button {
padding-left: 2rem;
padding-right: 2rem;
margin-right: 0.5em;
&:first-of-type {
margin-left: 0;
}
&:last-child {
margin-right: 0;
}
&:hover {
.buttonUnfollowLabel {
display: block;
}
.buttonSubscribedLabel {
display: none;
}
}
.buttonUnfollowLabel {
display: none;
}
}
}
.authorSubscribeSocialLabel {
display: none;
}
.authorSubscribeSocial {
align-items: center;
display: flex;
margin: 2rem 0;
.socialLink {
border: none;
display: inline-block;
height: 24px;
@ -165,251 +203,213 @@
position: absolute;
}
}
}
a[href*='facebook.com/'] {
&::before {
background-image: url(/icons/user-link-facebook.svg);
}
&:hover {
.authorSubscribeSocialLabel {
display: none;
}
}
}
a[href*='twitter.com/'] {
&::before {
background-image: url(/icons/user-link-twitter.svg);
}
&:hover {
.authorSubscribeSocialLabel {
display: none;
}
}
}
a[href*='telegram.com/'] {
&::before {
background-image: url(/icons/user-link-telegram.svg);
}
&:hover {
.authorSubscribeSocialLabel {
display: none;
}
}
}
a[href*='vk.cc/'],
a[href*='vk.com/'] {
&::before {
background-image: url(/icons/user-link-vk.svg);
}
&:hover {
.authorSubscribeSocialLabel {
display: none;
}
}
}
a[href*='tumblr.com/'] {
&::before {
background-image: url(/icons/user-link-tumblr.svg);
}
&:hover {
.authorSubscribeSocialLabel {
display: none;
}
}
}
a[href*='instagram.com/'] {
&::before {
background-image: url(/icons/user-link-instagram.svg);
}
&:hover {
.authorSubscribeSocialLabel {
display: none;
}
}
}
a[href*='behance.net/'] {
&::before {
background-image: url(/icons/user-link-behance.svg);
}
&:hover {
.authorSubscribeSocialLabel {
display: none;
}
}
}
a[href*='dribbble.com/'] {
&::before {
background-image: url(/icons/user-link-dribbble.svg);
}
&:hover {
.authorSubscribeSocialLabel {
display: none;
}
}
}
a[href*='github.com/'] {
&::before {
background-image: url(/icons/user-link-github.svg);
}
&:hover {
.authorSubscribeSocialLabel {
display: none;
}
}
}
a[href*='linkedin.com/'] {
&::before {
background-image: url(/icons/user-link-linkedin.svg);
}
&:hover {
.authorSubscribeSocialLabel {
display: none;
}
}
}
a[href*='medium.com/'] {
&::before {
background-image: url(/icons/user-link-medium.svg);
}
&:hover {
.authorSubscribeSocialLabel {
display: none;
}
}
}
a[href*='ok.ru/'] {
&::before {
background-image: url(/icons/user-link-ok.svg);
}
&:hover {
.authorSubscribeSocialLabel {
display: none;
}
}
}
a[href*='pinterest.com/'] {
&::before {
background-image: url(/icons/user-link-pinterest.svg);
}
&:hover {
.authorSubscribeSocialLabel {
display: none;
}
}
}
a[href*='reddit.com/'] {
&::before {
background-image: url(/icons/user-link-reddit.svg);
}
&:hover {
.authorSubscribeSocialLabel {
display: none;
}
}
}
a[href*='tiktok.com/'] {
&::before {
background-image: url(/icons/user-link-tiktok.svg);
}
&:hover {
.authorSubscribeSocialLabel {
display: none;
}
}
}
a[href*='youtube.com/'],
a[href*='youtu.be/'] {
&::before {
background-image: url(/icons/user-link-youtube.svg);
}
&:hover {
.authorSubscribeSocialLabel {
display: none;
}
}
}
a[href*='dzen.ru/'] {
&::before {
background-image: url(/icons/user-link-dzen.svg);
}
&:hover {
.authorSubscribeSocialLabel {
display: none;
}
}
}
.button {
padding-left: 2rem;
padding-right: 2rem;
margin-right: 0.5em;
&:first-of-type {
margin-left: 0;
}
&:last-child {
margin-right: 0;
}
&:hover {
.buttonUnfollowLabel {
display: block;
&[href*='facebook.com/'] {
&::before {
background-image: url(/icons/user-link-facebook.svg);
}
.buttonSubscribedLabel {
display: none;
&:hover {
.authorSubscribeSocialLabel {
display: none;
}
}
}
.buttonUnfollowLabel {
display: none;
&[href*='twitter.com/'] {
&::before {
background-image: url(/icons/user-link-twitter.svg);
}
&:hover {
.authorSubscribeSocialLabel {
display: none;
}
}
}
&[href*='telegram.com/'] {
&::before {
background-image: url(/icons/user-link-telegram.svg);
}
&:hover {
.authorSubscribeSocialLabel {
display: none;
}
}
}
&[href*='vk.cc/'],
&[href*='vk.com/'] {
&::before {
background-image: url(/icons/user-link-vk.svg);
}
&:hover {
.authorSubscribeSocialLabel {
display: none;
}
}
}
&[href*='tumblr.com/'] {
&::before {
background-image: url(/icons/user-link-tumblr.svg);
}
&:hover {
.authorSubscribeSocialLabel {
display: none;
}
}
}
&[href*='instagram.com/'] {
&::before {
background-image: url(/icons/user-link-instagram.svg);
}
&:hover {
.authorSubscribeSocialLabel {
display: none;
}
}
}
&[href*='behance.net/'] {
&::before {
background-image: url(/icons/user-link-behance.svg);
}
&:hover {
.authorSubscribeSocialLabel {
display: none;
}
}
}
&[href*='dribbble.com/'] {
&::before {
background-image: url(/icons/user-link-dribbble.svg);
}
&:hover {
.authorSubscribeSocialLabel {
display: none;
}
}
}
&[href*='github.com/'] {
&::before {
background-image: url(/icons/user-link-github.svg);
}
&:hover {
.authorSubscribeSocialLabel {
display: none;
}
}
}
&[href*='linkedin.com/'] {
&::before {
background-image: url(/icons/user-link-linkedin.svg);
}
&:hover {
.authorSubscribeSocialLabel {
display: none;
}
}
}
&[href*='medium.com/'] {
&::before {
background-image: url(/icons/user-link-medium.svg);
}
&:hover {
.authorSubscribeSocialLabel {
display: none;
}
}
}
&[href*='ok.ru/'] {
&::before {
background-image: url(/icons/user-link-ok.svg);
}
&:hover {
.authorSubscribeSocialLabel {
display: none;
}
}
}
&[href*='pinterest.com/'] {
&::before {
background-image: url(/icons/user-link-pinterest.svg);
}
&:hover {
.authorSubscribeSocialLabel {
display: none;
}
}
}
&[href*='reddit.com/'] {
&::before {
background-image: url(/icons/user-link-reddit.svg);
}
&:hover {
.authorSubscribeSocialLabel {
display: none;
}
}
}
&[href*='tiktok.com/'] {
&::before {
background-image: url(/icons/user-link-tiktok.svg);
}
&:hover {
.authorSubscribeSocialLabel {
display: none;
}
}
}
&[href*='youtube.com/'],
&[href*='youtu.be/'] {
&::before {
background-image: url(/icons/user-link-youtube.svg);
}
&:hover {
.authorSubscribeSocialLabel {
display: none;
}
}
}
&[href*='dzen.ru/'] {
&::before {
background-image: url(/icons/user-link-dzen.svg);
}
&:hover {
.authorSubscribeSocialLabel {
display: none;
}
}
}
}
}
.authorSubscribeSocialLabel {
display: none;
}
.authorSubscribeSocial {
align-items: center;
display: flex;
margin: 0 0.8rem 2rem 0;
@include media-breakpoint-down(sm) {
flex: 1 100%;
@ -626,6 +626,7 @@
flex-wrap: wrap;
font-size: 1.4rem;
margin-top: 0.5rem;
gap: 1rem;
@include media-breakpoint-down(md) {
justify-content: center;

View File

@ -2,7 +2,7 @@ import type { Author } from '../../../graphql/types.gen'
import { Userpic } from '../Userpic'
import { Icon } from '../../_shared/Icon'
import styles from './AuthorCard.module.scss'
import { createEffect, createMemo, createSignal, For, Show } from 'solid-js'
import { createEffect, createMemo, createSignal, For, Match, Show, Switch } from 'solid-js'
import { translit } from '../../../utils/ru2en'
import { follow, unfollow } from '../../../stores/zine/common'
import { clsx } from 'clsx'
@ -15,11 +15,15 @@ import { useLocalize } from '../../../context/localize'
import { ConditionalWrapper } from '../../_shared/ConditionalWrapper'
import { Modal } from '../../Nav/Modal'
import { showModal } from '../../../stores/ui'
import { getNumeralsDeclension } from '../../../utils/getNumeralsDeclension'
import { SubscriptionFilter } from '../../../pages/types'
import { isAuthor } from '../../../utils/isAuthor'
import { AuthorBadge } from '../AuthorBadge'
import { TopicBadge } from '../../Topic/TopicBadge'
import { Button } from '../../_shared/Button'
import { getShareUrl, SharePopup } from '../../Article/SharePopup'
import stylesHeader from '../../Nav/Header/Header.module.scss'
import { getDescription } from '../../../utils/meta'
import { Popover } from '../../_shared/Popover'
type Props = {
caption?: string
@ -43,6 +47,7 @@ type Props = {
following?: Array<Author | Topic>
showPublicationsCounter?: boolean
hideBio?: boolean
isCurrentUser?: boolean
}
export const AuthorCard = (props: Props) => {
@ -130,7 +135,6 @@ export const AuthorCard = (props: Props) => {
if (props.isAuthorPage && props.author.userpic?.includes('assets.discours.io')) {
setUserpicUrl(props.author.userpic.replace('100x', '500x500'))
}
return (
<>
<div
@ -219,74 +223,89 @@ export const AuthorCard = (props: Props) => {
}
>
<div class={styles.subscribersContainer}>
<Show when={props.followers && props.followers.length > 0}>
<div
class={styles.subscribers}
onClick={() => {
redirectPage(router, 'authorFollowers', { slug: props.author.slug })
showModal('followers')
}}
>
<For each={props.followers.slice(0, 3)}>
{(f) => <Userpic name={f.name} userpic={f.userpic} class={styles.userpic} />}
</For>
<div class={styles.subscribersCounter}>
{props.followers.length}&nbsp;
{getNumeralsDeclension(props.followers.length, [
t('subscriber'),
t('subscriber_rp'),
t('subscribers')
])}
</div>
</div>
</Show>
<Show when={props.following && props.following.length > 0}>
<div
class={styles.subscribers}
onClick={() => {
redirectPage(router, 'authorFollowing', { slug: props.author.slug })
showModal('following')
}}
>
<For each={props.following.slice(0, 3)}>
{(f) => {
if ('name' in f) {
return <Userpic name={f.name} userpic={f.userpic} class={styles.userpic} />
} else if ('title' in f) {
return <Userpic name={f.title} userpic={f.pic} class={styles.userpic} />
}
return null
<Switch>
<Match when={props.followers && props.followers.length > 0 && !props.isCurrentUser}>
<div
class={styles.subscribers}
onClick={() => {
redirectPage(router, 'authorFollowers', { slug: props.author.slug })
showModal('followers')
}}
</For>
<div class={styles.subscribersCounter}>
{props.following.length}&nbsp;
{getNumeralsDeclension(props.following.length, [
t('subscription'),
t('subscription_rp'),
t('subscriptions')
])}
>
<For each={props.followers.slice(0, 3)}>
{(f) => <Userpic name={f.name} userpic={f.userpic} class={styles.userpic} />}
</For>
<div class={styles.subscribersCounter}>
{t('SubscriptionWithCount', { count: props.followers.length })}
</div>
</div>
</div>
</Show>
</Match>
<Match when={props.followers && props.followers.length > 0 && props.isCurrentUser}>
<Button
variant="secondary"
onClick={() => redirectPage(router, 'profileSettings')}
value={t('Edit profile')}
/>
</Match>
</Switch>
<Switch>
<Match when={props.following && props.following.length > 0 && !props.isCurrentUser}>
<div
class={styles.subscribers}
onClick={() => {
redirectPage(router, 'authorFollowing', { slug: props.author.slug })
showModal('following')
}}
>
<For each={props.following.slice(0, 3)}>
{(f) => {
if ('name' in f) {
return <Userpic name={f.name} userpic={f.userpic} class={styles.userpic} />
} else if ('title' in f) {
return <Userpic name={f.title} userpic={f.pic} class={styles.userpic} />
}
return null
}}
</For>
<div class={styles.subscribersCounter}>
{t('SubscriberWithCount', { count: props?.following.length ?? 0 })}
</div>
</div>
</Match>
<Match when={props.following && props.following.length > 0 && props.isCurrentUser}>
<SharePopup
containerCssClass={stylesHeader.control}
title={props.author.name}
description={props.author.bio}
imageUrl={props.author.userpic}
shareUrl={getShareUrl({ pathname: `/author/${props.author.slug}` })}
trigger={<Button variant="secondary" value={t('Share')} />}
/>
</Match>
</Switch>
</div>
</Show>
</div>
<ShowOnlyOnClient>
<Show when={isSessionLoaded()}>
<div class={styles.authorSubscribeSocial}>
<For each={props.author.links}>
{(link) => (
<a
class={styles.socialLink}
href={link.startsWith('http') ? link : `https://${link}`}
target="_blank"
rel="nofollow noopener"
>
<span class={styles.authorSubscribeSocialLabel}>
{link.startsWith('http') ? link : `https://${link}`}
</span>
</a>
)}
</For>
</div>
<Show when={canFollow()}>
<div class={styles.authorSubscribe}>
<Show when={!props.noSocialButtons && !props.hideWriteButton && props.author.links}>
<div class={styles.authorSubscribeSocial}>
<For each={props.author.links}>
{(link) => (
<a href={link}>
<span class={styles.authorSubscribeSocialLabel}>{link}</span>
</a>
)}
</For>
</div>
</Show>
<Show
when={subscribed()}
fallback={

View File

@ -18,6 +18,7 @@ import { useLocalize } from '../../../context/localize'
import { AuthorRatingControl } from '../../Author/AuthorRatingControl'
import { hideModal } from '../../../stores/ui'
import { getPagePath } from '@nanostores/router'
import { useSession } from '../../../context/session'
type Props = {
shouts: Shout[]
@ -34,6 +35,7 @@ export const AuthorView = (props: Props) => {
const { authorEntities } = useAuthorsStore({ authors: [props.author] })
const { page } = useRouter()
const { user } = useSession()
const author = createMemo(() => authorEntities()[props.authorSlug])
const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false)
const [isBioExpanded, setIsBioExpanded] = createSignal(false)
@ -131,6 +133,7 @@ export const AuthorView = (props: Props) => {
isAuthorPage={true}
followers={followers()}
following={following()}
isCurrentUser={author().slug === user()?.slug}
/>
</div>
</Show>
@ -139,13 +142,13 @@ export const AuthorView = (props: Props) => {
<ul class="view-switcher">
<li classList={{ 'view-switcher__item--selected': page().route === 'author' }}>
<a href={getPagePath(router, 'author', { slug: props.authorSlug })}>{t('Publications')}</a>
<span class="view-switcher__counter">{author().stat.shouts}</span>
<span class="view-switcher__counter">{author().stat?.shouts}</span>
</li>
<li classList={{ 'view-switcher__item--selected': page().route === 'authorComments' }}>
<a href={getPagePath(router, 'authorComments', { slug: props.authorSlug })}>
{t('Comments')}
</a>
<span class="view-switcher__counter">{author().stat.commented}</span>
<span class="view-switcher__counter">{author().stat?.commented}</span>
</li>
<li classList={{ 'view-switcher__item--selected': page().route === 'authorAbout' }}>
<a

View File

@ -174,14 +174,15 @@ export type Mutation = {
destroyTopic: Result
follow: Result
getSession: AuthResult
markAllNotificationsAsRead: Result
markAsRead: Result
markNotificationAsRead: Result
rateUser: Result
registerUser: AuthResult
sendLink: Result
unfollow: Result
updateChat: Result
updateMessage: Result
updateOnlineStatus: Result
updateProfile: Result
updateReaction: Result
updateShout: Result
@ -246,6 +247,10 @@ export type MutationMarkAsReadArgs = {
ids: Array<InputMaybe<Scalars['Int']>>
}
export type MutationMarkNotificationAsReadArgs = {
notification_id: Scalars['Int']
}
export type MutationRateUserArgs = {
slug: Scalars['String']
value: Scalars['Int']
@ -297,6 +302,33 @@ export type MutationUpdateTopicArgs = {
input: TopicInput
}
export type Notification = {
createdAt: Scalars['DateTime']
data?: Maybe<Scalars['String']>
id: Scalars['Int']
occurrences: Scalars['Int']
reaction?: Maybe<Scalars['Int']>
seen: Scalars['Boolean']
shout?: Maybe<Scalars['Int']>
type?: Maybe<NotificationType>
}
export enum NotificationType {
NewComment = 'NEW_COMMENT',
NewReply = 'NEW_REPLY'
}
export type NotificationsQueryParams = {
limit?: InputMaybe<Scalars['Int']>
offset?: InputMaybe<Scalars['Int']>
}
export type NotificationsQueryResult = {
notifications: Array<Maybe<Notification>>
totalCount: Scalars['Int']
totalUnreadCount: Scalars['Int']
}
export type Operation = {
id: Scalars['Int']
name: Scalars['String']
@ -325,6 +357,7 @@ export type Query = {
loadChats: Result
loadDrafts: Array<Maybe<Shout>>
loadMessagesBy: Result
loadNotifications: NotificationsQueryResult
loadReactionsBy: Array<Maybe<Reaction>>
loadRecipients: Result
loadShout?: Maybe<Shout>
@ -373,6 +406,10 @@ export type QueryLoadMessagesByArgs = {
offset?: InputMaybe<Scalars['Int']>
}
export type QueryLoadNotificationsArgs = {
params: NotificationsQueryParams
}
export type QueryLoadReactionsByArgs = {
by: ReactionBy
limit?: InputMaybe<Scalars['Int']>
@ -599,12 +636,6 @@ export type Stat = {
viewed?: Maybe<Scalars['Int']>
}
export type Subscription = {
newMessage?: Maybe<Message>
newReaction?: Maybe<Reaction>
newShout?: Maybe<Shout>
}
export type Token = {
createdAt: Scalars['DateTime']
expiresAt?: Maybe<Scalars['DateTime']>