- Show profile tabs (comments and about)
- Save profile fields

Co-authored-by: ilya-bkv <i.yablokov@ccmp.me>
This commit is contained in:
kvakazyambra 2023-01-08 18:45:33 +04:00 committed by GitHub
parent 838c4baa90
commit dc6dda4745
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 249 additions and 63 deletions

View File

@ -16,8 +16,7 @@
justify-content: center; justify-content: center;
height: 0.9em; height: 0.9em;
line-height: 0; line-height: 0;
@include font-size(3.6rem); font-size: 1.6em;
padding: 0; padding: 0;
width: 0.9em; width: 0.9em;

View File

@ -54,6 +54,10 @@
padding: 0 0 0 42px; padding: 0 0 0 42px;
} }
@include media-breakpoint-down(md) {
flex-wrap: wrap;
}
a { a {
background: #f7f7f7; background: #f7f7f7;
border: none; border: none;
@ -136,6 +140,17 @@
} }
} }
.authorSubscribeSocial {
align-items: center;
display: flex;
@include media-breakpoint-down(sm) {
flex: 1 100%;
justify-content: center;
margin-top: 1em;
}
}
.buttonSubscribe { .buttonSubscribe {
align-items: center; align-items: center;
aspect-ratio: 1/1; aspect-ratio: 1/1;
@ -180,9 +195,12 @@
} }
.authorPage { .authorPage {
@include media-breakpoint-down(md) {
justify-content: center;
}
.authorName { .authorName {
@include font-size(3.4rem); @include font-size(3.4rem);
font-weight: 500; font-weight: 500;
margin-bottom: 0.2em; margin-bottom: 0.2em;
} }
@ -195,10 +213,18 @@
.authorSubscribe { .authorSubscribe {
margin-top: 2rem; margin-top: 2rem;
padding-left: 0; padding-left: 0;
@include media-breakpoint-down(md) {
justify-content: center;
}
} }
.authorDetails { .authorDetails {
display: block; display: block;
@include media-breakpoint-down(md) {
flex: 1 100%;
}
} }
.buttonLabel { .buttonLabel {
@ -237,6 +263,16 @@
.button { .button {
margin-right: 1.6rem; margin-right: 1.6rem;
vertical-align: middle; vertical-align: middle;
&:last-of-type {
margin-right: 0;
}
@include media-breakpoint-down(sm) {
display: block;
margin-bottom: 0.5em;
margin-right: 0;
}
} }
} }

View File

@ -177,7 +177,9 @@ export const AuthorCard = (props: AuthorCardProps) => {
</button> </button>
<Show when={!props.noSocialButtons}> <Show when={!props.noSocialButtons}>
<For each={props.author.links}>{(link) => <a href={link} />}</For> <div class={styles.authorSubscribeSocial}>
<For each={props.author.links}>{(link) => <a href={link} />}</For>
</div>
</Show> </Show>
</Show> </Show>
</div> </div>

View File

@ -1,11 +1,22 @@
.user-details { .user-details {
margin-bottom: 5.4rem; margin: 0 0 5.4rem;
@include media-breakpoint-up(md) {
margin-left: 160px;
}
@include media-breakpoint-up(lg) {
margin-left: 235px;
}
@include media-breakpoint-down(md) {
text-align: center;
}
} }
.author-page { .author-page {
.view-switcher { .view-switcher {
@include font-size(1.5rem); @include font-size(1.5rem);
margin-top: 0; margin-top: 0;
button { button {

View File

@ -5,7 +5,7 @@ import './Full.scss'
export const AuthorFull = (props: { author: Author }) => { export const AuthorFull = (props: { author: Author }) => {
return ( return (
<div class="row"> <div class="row">
<div class="col-md-8 offset-md-2 user-details"> <div class="col-md-9 col-lg-8 user-details">
<AuthorCard author={props.author} compact={false} isAuthorPage={true} /> <AuthorCard author={props.author} compact={false} isAuthorPage={true} />
</div> </div>
</div> </div>

View File

@ -35,12 +35,16 @@
} }
.big.circlewrap { .big.circlewrap {
margin-right: 4.8rem; 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) {
margin-right: 4.8rem;
}
.userpic { .userpic {
font-size: 2em; font-size: 2em;
line-height: 168px; line-height: 168px;

View File

@ -0,0 +1,55 @@
.ratingContainer {
@include font-size(1.5rem);
display: inline-block;
vertical-align: top;
}
.ratingControl {
@include font-size(1.5rem);
display: inline-flex;
margin-left: 1em;
vertical-align: middle;
}
.additionalControls {
white-space: nowrap;
@include media-breakpoint-up(md) {
text-align: right;
}
}
.userpic {
background: #fff;
box-shadow: 0 0 0 2px #fff;
display: inline-block;
margin-right: -1.2rem;
vertical-align: top;
}
.subscribers {
cursor: pointer;
display: inline-block;
margin: -0.4rem 2em 0 0;
vertical-align: top;
}
.subscribersCounter {
background: #fff;
border: 2px solid #000;
border-radius: 100%;
@include font-size(1rem);
font-weight: bold;
height: 32px;
line-height: 30px;
position: relative;
text-align: center;
width: 32px;
z-index: 1;
}
.subscribersList {
max-height: 15em;
overflow: auto;
position: relative;
}

View File

@ -1,7 +1,7 @@
import { Show, createMemo, createSignal, For, onMount } from 'solid-js' import { Show, createMemo, createSignal, Switch, onMount, For, Match, createEffect } from 'solid-js'
import type { Author, Shout } from '../../graphql/types.gen' import type { Author, Shout } from '../../graphql/types.gen'
import { Row1 } from '../Feed/Row1'
import { Row2 } from '../Feed/Row2' import { Row2 } from '../Feed/Row2'
import { Row3 } from '../Feed/Row3'
import { AuthorFull } from '../Author/Full' import { AuthorFull } from '../Author/Full'
import { t } from '../../utils/intl' import { t } from '../../utils/intl'
import { useAuthorsStore } from '../../stores/zine/authors' import { useAuthorsStore } from '../../stores/zine/authors'
@ -9,9 +9,17 @@ import { loadShouts, useArticlesStore } from '../../stores/zine/articles'
import { useTopicsStore } from '../../stores/zine/topics' import { useTopicsStore } from '../../stores/zine/topics'
import { useRouter } from '../../stores/router' import { useRouter } from '../../stores/router'
import { Beside } from '../Feed/Beside'
import { restoreScrollPosition, saveScrollPosition } from '../../utils/scroll' import { restoreScrollPosition, saveScrollPosition } from '../../utils/scroll'
import { splitToPages } from '../../utils/splitToPages' import { splitToPages } from '../../utils/splitToPages'
import { RatingControl } from '../Article/RatingControl'
import styles from './Author.module.scss'
import { clsx } from 'clsx'
import Userpic from '../Author/Userpic'
import { Popup } from '../_shared/Popup'
import { AuthorCard } from '../Author/Card'
import { loadReactionsBy, REACTIONS_AMOUNT_PER_PAGE } from '../../stores/zine/reactions'
import { apiClient } from '../../utils/apiClient'
import Comment from '../Article/Comment'
// TODO: load reactions on client // TODO: load reactions on client
type AuthorProps = { type AuthorProps = {
@ -23,7 +31,7 @@ type AuthorProps = {
} }
type AuthorPageSearchParams = { type AuthorPageSearchParams = {
by: '' | 'viewed' | 'rating' | 'commented' | 'recent' | 'followed' by: '' | 'viewed' | 'rating' | 'commented' | 'recent' | 'followed' | 'about' | 'popular'
} }
export const PRERENDERED_ARTICLES_COUNT = 12 export const PRERENDERED_ARTICLES_COUNT = 12
@ -38,6 +46,7 @@ export const AuthorView = (props: AuthorProps) => {
const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false) const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false)
const author = createMemo(() => authorEntities()[props.authorSlug]) const author = createMemo(() => authorEntities()[props.authorSlug])
const subscribers = Array.from({ length: 12 }).fill(author())
const { searchParams, changeSearchParam } = useRouter<AuthorPageSearchParams>() const { searchParams, changeSearchParam } = useRouter<AuthorPageSearchParams>()
const loadMore = async () => { const loadMore = async () => {
@ -69,6 +78,23 @@ export const AuthorView = (props: AuthorProps) => {
splitToPages(sortedArticles(), PRERENDERED_ARTICLES_COUNT, LOAD_MORE_PAGE_SIZE) splitToPages(sortedArticles(), PRERENDERED_ARTICLES_COUNT, LOAD_MORE_PAGE_SIZE)
) )
console.log('!!! authorEntities():', author())
const [commented, setCommented] = createSignal([])
createEffect(async () => {
if (searchParams().by === 'commented') {
try {
const data = await apiClient.getReactionsBy({
by: { comment: true, createdBy: props.authorSlug },
limit: 100,
offset: 0
})
setCommented(data)
} catch (error) {
console.log('!!! error:', error)
}
}
})
return ( return (
<div class="author-page"> <div class="author-page">
<Show when={author()} fallback={<div class="center">{t('Loading')}</div>}> <Show when={author()} fallback={<div class="center">{t('Loading')}</div>}>
@ -89,52 +115,91 @@ export const AuthorView = (props: AuthorProps) => {
</li> </li>
<li classList={{ selected: searchParams().by === 'commented' }}> <li classList={{ selected: searchParams().by === 'commented' }}>
<button type="button" onClick={() => changeSearchParam('by', 'commented')}> <button type="button" onClick={() => changeSearchParam('by', 'commented')}>
{t('Discussing')} {t('Comments')}
</button>
</li>
<li classList={{ selected: searchParams().by === 'popular' }}>
<button type="button" onClick={() => changeSearchParam('by', 'popular')}>
Популярное
</button>
</li>
<li classList={{ selected: searchParams().by === 'about' }}>
<button type="button" onClick={() => changeSearchParam('by', 'about')}>
О себе
</button> </button>
</li> </li>
</ul> </ul>
</div> </div>
<div class="col-md-4"> <div class={clsx('col-md-4', styles.additionalControls)}>
<div class="mode-switcher"> <Popup
{`${t('Show')} `} {...props}
<span class="mode-switcher__control">{t('All posts')}</span> trigger={
<div class={styles.subscribers}>
<Userpic user={author()} class={styles.userpic} />
<Userpic user={author()} class={styles.userpic} />
<Userpic user={author()} class={styles.userpic} />
<div class={clsx(styles.userpic, styles.subscribersCounter)}>12</div>
</div>
}
variant="tiny"
>
<ul class={clsx('nodash', styles.subscribersList)}>
<For each={subscribers}>
{(item: Author) => (
<li>
<AuthorCard author={item} hideDescription={true} hideFollow={true} hasLink={true} />
</li>
)}
</For>
</ul>
</Popup>
<div class={styles.ratingContainer}>
Карма
<RatingControl rating={19} class={styles.ratingControl} />
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<Beside <Switch fallback={<p>дефолтное состояние</p>}>
title={t('Topics which supported by author')} <Match when={searchParams().by === 'about'}>
values={topicsByAuthor()[author().slug]?.slice(0, 5)} <h1>About</h1>
beside={sortedArticles()[0]} <p>{JSON.stringify(authorEntities())}</p>
wrapper={'topic'} </Match>
topicShortDescription={true} <Match when={searchParams().by === 'commented'}>
isTopicCompact={true} <For each={commented()}>{(comment) => <Comment comment={comment} />}</For>
isTopicInRow={true} </Match>
iconButton={true} <Match when={searchParams().by === 'popular'}>
/> <Row1 article={sortedArticles()[0]} />
<Row3 articles={sortedArticles().slice(1, 4)} /> <Row2 articles={sortedArticles().slice(1, 3)} isEqual={true} />
<Row2 articles={sortedArticles().slice(4, 6)} /> <Row1 article={sortedArticles()[3]} />
<Row3 articles={sortedArticles().slice(6, 9)} /> <Row2 articles={sortedArticles().slice(4, 6)} isEqual={true} />
<Row3 articles={sortedArticles().slice(9, 12)} /> <Row1 article={sortedArticles()[6]} />
<Row2 articles={sortedArticles().slice(7, 9)} isEqual={true} />
<For each={pages()}> <For each={pages()}>
{(page) => ( {(page) => (
<> <>
<Row3 articles={page.slice(0, 3)} /> <Row1 article={page[0]} />
<Row3 articles={page.slice(3, 6)} /> <Row2 articles={page.slice(1, 3)} isEqual={true} />
<Row3 articles={page.slice(6, 9)} /> <Row1 article={page[3]} />
</> <Row2 articles={page.slice(4, 6)} isEqual={true} />
)} <Row1 article={page[6]} />
</For> <Row2 articles={page.slice(7, 9)} isEqual={true} />
</>
)}
</For>
<Show when={isLoadMoreButtonVisible()}> <Show when={isLoadMoreButtonVisible()}>
<p class="load-more-container"> <p class="load-more-container">
<button class="button" onClick={loadMore}> <button class="button" onClick={loadMore}>
{t('Load more')} {t('Load more')}
</button> </button>
</p> </p>
</Show> </Show>
</Match>
</Switch>
</Show> </Show>
</div> </div>
) )

View File

@ -4,18 +4,19 @@
.popup { .popup {
background: #fff; background: #fff;
top: calc(100% + 8px);
opacity: 1;
color: #000; color: #000;
position: absolute;
z-index: 100;
min-width: 144px; min-width: 144px;
opacity: 1;
position: absolute;
top: calc(100% + 8px);
z-index: 100;
ul { ul {
margin-bottom: 0; margin-bottom: 0;
li { li {
position: relative; position: relative;
&:last-child { &:last-child {
margin-bottom: 0; margin-bottom: 0;
} }
@ -24,11 +25,12 @@
&.bordered { &.bordered {
@include font-size(1.6rem); @include font-size(1.6rem);
border: 2px solid #000; border: 2px solid #000;
padding: 2.4rem; padding: 2.4rem;
ul li { ul li {
margin-bottom: 1.6rem; margin-bottom: 1.6rem;
&:last-child { &:last-child {
margin-bottom: 0; margin-bottom: 0;
} }
@ -37,11 +39,12 @@
&.tiny { &.tiny {
@include font-size(1.4rem); @include font-size(1.4rem);
box-shadow: 0 4px 60px rgba(0, 0, 0, 0.1); box-shadow: 0 4px 60px rgba(0, 0, 0, 0.1);
padding: 1rem; padding: 1rem;
ul li { ul li {
margin-bottom: 1rem; margin-bottom: 1rem;
&:last-child { &:last-child {
margin-bottom: 0; margin-bottom: 0;
} }
@ -67,22 +70,22 @@
white-space: nowrap; white-space: nowrap;
&:hover { &:hover {
img { .icon img {
filter: invert(0); filter: invert(0);
} }
} }
} }
img {
filter: invert(1);
max-height: 2rem;
max-width: 2rem;
transition: filter 0.3s;
}
.icon { .icon {
display: inline-block; display: inline-block;
width: 3.6rem; width: 3.6rem;
img {
filter: invert(1);
max-height: 2rem;
max-width: 2rem;
transition: filter 0.3s;
}
} }
} }

View File

@ -52,6 +52,7 @@
"Fill email": "Введите почту", "Fill email": "Введите почту",
"Follow": "Подписаться", "Follow": "Подписаться",
"Follow the topic": "Подписаться на тему", "Follow the topic": "Подписаться на тему",
"Followers": "Подписчики",
"Forgot password?": "Забыли пароль?", "Forgot password?": "Забыли пароль?",
"Full name": "Имя и фамилия", "Full name": "Имя и фамилия",
"Get to know the most intelligent people of our time, edit and discuss the articles, share your expertise, rate and decide what to publish in the magazine": "Познакомитесь с выдающимися людьми нашего времени, участвуйте в редактировании и обсуждении статей, выступайте экспертом, оценивайте материалы других авторов со всего мира и определяйте, какие статьи будут опубликованы в журнале", "Get to know the most intelligent people of our time, edit and discuss the articles, share your expertise, rate and decide what to publish in the magazine": "Познакомитесь с выдающимися людьми нашего времени, участвуйте в редактировании и обсуждении статей, выступайте экспертом, оценивайте материалы других авторов со всего мира и определяйте, какие статьи будут опубликованы в журнале",

View File

@ -12,7 +12,8 @@ import type {
MutationCreateMessageArgs, MutationCreateMessageArgs,
Chat, Chat,
QueryLoadRecipientsArgs, QueryLoadRecipientsArgs,
ProfileInput ProfileInput,
ReactionBy
} from '../graphql/types.gen' } from '../graphql/types.gen'
import { publicGraphQLClient } from '../graphql/publicGraphQLClient' import { publicGraphQLClient } from '../graphql/publicGraphQLClient'
import { getToken, privateGraphQLClient } from '../graphql/privateGraphQLClient' import { getToken, privateGraphQLClient } from '../graphql/privateGraphQLClient'
@ -269,7 +270,16 @@ export const apiClient = {
if (resp.error) console.debug(resp) if (resp.error) console.debug(resp)
return resp.data.loadShouts return resp.data.loadShouts
}, },
getReactionsBy: async ({ by, limit = REACTIONS_AMOUNT_PER_PAGE, offset = 0 }) => {
getReactionsBy: async ({
by,
limit = REACTIONS_AMOUNT_PER_PAGE,
offset = 0
}: {
by: ReactionBy
limit: number
offset: number
}) => {
const resp = await publicGraphQLClient.query(reactionsLoadBy, { by, limit, offset }).toPromise() const resp = await publicGraphQLClient.query(reactionsLoadBy, { by, limit, offset }).toPromise()
console.debug(resp) console.debug(resp)
return resp.data.loadReactionsBy return resp.data.loadReactionsBy