Fix topic subscriptions status

This commit is contained in:
ilya-bkv 2024-02-08 12:11:52 +03:00
parent 91ba53a1ee
commit 6b2cadfcb6
12 changed files with 154 additions and 139 deletions

View File

@ -2,7 +2,7 @@
"$schema": "https://biomejs.dev/schemas/1.5.3/schema.json",
"files": {
"include": ["*.tsx", "*.ts", "*.js", "*.json"],
"ignore": ["./dist", "./node_modules", ".husky", "docs", "gen"]
"ignore": ["./dist", "./node_modules", ".husky", "docs", "gen", "*.d.ts"]
},
"vcs": {
"defaultBranch": "dev",
@ -29,7 +29,7 @@
}
},
"linter": {
"ignore": ["*.scss", "*.md", ".DS_Store", "*.svg"],
"ignore": ["*.scss", "*.md", ".DS_Store", "*.svg", "*.d.ts"],
"enabled": true,
"rules": {
"all": true,

View File

@ -16,13 +16,10 @@ import { ConditionalWrapper } from '../../_shared/ConditionalWrapper'
import { Icon } from '../../_shared/Icon'
import { Userpic } from '../Userpic'
import { FollowedInfo } from '../../../pages/types'
import stylesButton from '../../_shared/Button/Button.module.scss'
import styles from './AuthorBadge.module.scss'
type FollowedInfo = {
value?: boolean
loaded?: boolean
}
type Props = {
author: Author
minimizeSubscribeButton?: boolean

View File

@ -308,7 +308,13 @@ export const AuthorCard = (props: Props) => {
author={subscription}
/>
) : (
<TopicBadge topic={subscription} />
<TopicBadge
isFollowed={{
loaded: Boolean(authorSubs()),
value: isOwnerSubscribed(subscription.id),
}}
topic={subscription}
/>
)
}
</For>

View File

@ -1,10 +1,13 @@
.TopicBadge {
display: flex;
flex-direction: row;
align-items: flex-start;
margin-bottom: 2rem;
gap: 1rem;
.content {
align-items: flex-start;
display: flex;
flex-direction: row;
margin-bottom: .8rem;
}
.basicInfo {
display: flex;
flex-flow: row nowrap;
@ -78,3 +81,34 @@
width: 9em;
}
}
.stats {
@include font-size(1.5rem);
color: var(--secondary-color);
display: flex;
margin: 0 0 1em;
@include media-breakpoint-down(md) {
flex-wrap: wrap;
}
@include media-breakpoint-down(sm) {
margin-top: 0.5em;
}
.statsItem {
@include font-size(1.4rem);
margin-right: 1.6rem;
white-space: nowrap;
&:last-child {
margin-right: 0;
}
&.followers {
word-break: keep-all;
}
}
}

View File

@ -1,5 +1,5 @@
import { clsx } from 'clsx'
import { Show, createEffect, createSignal } from 'solid-js'
import { Show, createEffect, createSignal, on } from 'solid-js'
import { useFollowing } from '../../../context/following'
import { useLocalize } from '../../../context/localize'
@ -11,11 +11,14 @@ import { getImageUrl } from '../../../utils/getImageUrl'
import { Button } from '../../_shared/Button'
import { CheckButton } from '../../_shared/CheckButton'
import { FollowedInfo } from '../../../pages/types'
import styles from './TopicBadge.module.scss'
type Props = {
topic: Topic
minimizeSubscribeButton?: boolean
isFollowed?: FollowedInfo
showStat?: boolean
}
export const TopicBadge = (props: Props) => {
@ -24,12 +27,12 @@ export const TopicBadge = (props: Props) => {
const [isMobileView, setIsMobileView] = createSignal(false)
const { requireAuthentication } = useSession()
const { setFollowing, loading: subLoading } = useFollowing()
const [followed, setFollowed] = createSignal()
const [isFollowed, setIsFollowed] = createSignal<boolean>()
const handleFollowClick = () => {
const value = !followed()
const value = !isFollowed()
requireAuthentication(() => {
setFollowed(value)
setIsFollowed(value)
setFollowing(FollowingEntity.Topic, props.topic.slug, value)
}, 'subscribe')
}
@ -38,67 +41,85 @@ export const TopicBadge = (props: Props) => {
setIsMobileView(!mediaMatches.sm)
})
createEffect(
on(
() => props.isFollowed,
() => {
setIsFollowed(props.isFollowed.value)
},
),
)
const title = () =>
lang() === 'en' ? capitalize(props.topic.slug.replaceAll('-', ' ')) : props.topic.title
return (
<div class={styles.TopicBadge}>
<div class={styles.basicInfo}>
<a
href={`/topic/${props.topic.slug}`}
class={clsx(styles.picture, {
[styles.withImage]: props.topic.pic,
[styles.smallSize]: isMobileView(),
})}
style={
props.topic.pic && {
'background-image': `url('${getImageUrl(props.topic.pic, { width: 40, height: 40 })}')`,
<div class={styles.content}>
<div class={styles.basicInfo}>
<a
href={`/topic/${props.topic.slug}`}
class={clsx(styles.picture, {
[styles.withImage]: props.topic.pic,
[styles.smallSize]: isMobileView(),
})}
style={
props.topic.pic && {
'background-image': `url('${getImageUrl(props.topic.pic, { width: 40, height: 40 })}')`,
}
}
}
/>
<a href={`/topic/${props.topic.slug}`} class={styles.info}>
<span class={styles.title}>{title()}</span>
/>
<a href={`/topic/${props.topic.slug}`} class={styles.info}>
<span class={styles.title}>{title()}</span>
<Show
when={props.topic.body}
fallback={
<div class={styles.description}>
{t('PublicationsWithCount', { count: props.topic.stat.shouts ?? 0 })}
</div>
}
>
<div class={clsx('text-truncate', styles.description)}>{props.topic.body}</div>
</Show>
</a>
</div>
<div class={styles.actions}>
<Show
when={props.topic.body}
when={!props.minimizeSubscribeButton}
fallback={
<div class={styles.description}>
{t('PublicationsWithCount', { count: props.topic.stat.shouts ?? 0 })}
</div>
<CheckButton text={t('Follow')} checked={Boolean(isFollowed())} onClick={handleFollowClick} />
}
>
<div class={clsx('text-truncate', styles.description)}>{props.topic.body}</div>
</Show>
</a>
</div>
<div class={styles.actions}>
<Show
when={!props.minimizeSubscribeButton}
fallback={
<CheckButton text={t('Follow')} checked={Boolean(followed())} onClick={handleFollowClick} />
}
>
<Show
when={followed()}
fallback={
<Show
when={isFollowed()}
fallback={
<Button
variant="primary"
size="S"
value={subLoading() ? t('subscribing...') : t('Subscribe')}
onClick={handleFollowClick}
class={styles.subscribeButton}
/>
}
>
<Button
variant="primary"
size="S"
value={subLoading() ? t('subscribing...') : t('Subscribe')}
onClick={handleFollowClick}
variant="bordered"
size="S"
value={t('Following')}
class={styles.subscribeButton}
/>
}
>
<Button
onClick={handleFollowClick}
variant="bordered"
size="S"
value={t('Following')}
class={styles.subscribeButton}
/>
</Show>
</Show>
</Show>
</div>
</div>
<div class={styles.stats}>
<span class={styles.statsItem}>{t('shoutsWithCount', { count: props.topic?.stat?.shouts })}</span>
<span class={styles.statsItem}>{t('authorsWithCount', { count: props.topic?.stat?.authors })}</span>
<span class={styles.statsItem}>
{t('followersWithCount', { count: props.topic?.stat?.followers })}
</span>
</div>
</div>
)

View File

@ -32,45 +32,6 @@
}
}
.stats {
@include font-size(1.7rem);
color: #9fa1a7;
display: flex;
margin: 0 0 1em;
@include media-breakpoint-down(md) {
flex-wrap: wrap;
}
@include media-breakpoint-down(sm) {
margin-top: 0.5em;
}
.statsItem {
@include font-size(1.5rem);
margin-right: 1.6rem;
white-space: nowrap;
&:last-child {
margin-right: 0;
}
&.compact {
font-size: small;
}
&.followers {
word-break: keep-all;
}
&.button {
float: right;
}
}
}
.loadMoreContainer {
margin-top: 48px;
text-align: center;

View File

@ -1,21 +1,22 @@
import type { Topic } from '../../graphql/schema/core.gen'
import type { Topic } from '../../../graphql/schema/core.gen'
import { Meta } from '@solidjs/meta'
import { clsx } from 'clsx'
import { For, Show, createEffect, createMemo, createSignal } from 'solid-js'
import { useFollowing } from '../../context/following'
import { useLocalize } from '../../context/localize'
import { useRouter } from '../../stores/router'
import { setTopicsSort, useTopicsStore } from '../../stores/zine/topics'
import { capitalize } from '../../utils/capitalize'
import { dummyFilter } from '../../utils/dummyFilter'
import { getImageUrl } from '../../utils/getImageUrl'
import { scrollHandler } from '../../utils/scroll'
import { TopicCard } from '../Topic/Card'
import { Loading } from '../_shared/Loading'
import { SearchField } from '../_shared/SearchField'
import { useFollowing } from '../../../context/following'
import { useLocalize } from '../../../context/localize'
import { useRouter } from '../../../stores/router'
import { setTopicsSort, useTopicsStore } from '../../../stores/zine/topics'
import { capitalize } from '../../../utils/capitalize'
import { dummyFilter } from '../../../utils/dummyFilter'
import { getImageUrl } from '../../../utils/getImageUrl'
import { scrollHandler } from '../../../utils/scroll'
import { TopicCard } from '../../Topic/Card'
import { Loading } from '../../_shared/Loading'
import { SearchField } from '../../_shared/SearchField'
import { TopicBadge } from '../../Topic/TopicBadge'
import styles from './AllTopics.module.scss'
type AllTopicsPageSearchParams = {
@ -29,7 +30,7 @@ type Props = {
const PAGE_SIZE = 20
export const AllTopicsView = (props: Props) => {
export const AllTopics = (props: Props) => {
const { t, lang } = useLocalize()
const { searchParams, changeSearchParams } = useRouter<AllTopicsPageSearchParams>()
const [limit, setLimit] = createSignal(PAGE_SIZE)
@ -41,8 +42,6 @@ export const AllTopicsView = (props: Props) => {
sortBy: searchParams().by || 'shouts',
})
const { subscriptions } = useFollowing()
createEffect(() => {
if (!searchParams().by) {
changeSearchParams({
@ -76,7 +75,7 @@ export const AllTopicsView = (props: Props) => {
return keys
})
const subscribed = (topicSlug: string) => subscriptions.topics.some((topic) => topic.slug === topicSlug)
const { isOwnerSubscribed } = useFollowing()
const showMore = () => setLimit((oldLimit) => oldLimit + PAGE_SIZE)
const [searchQuery, setSearchQuery] = createSignal('')
@ -186,28 +185,18 @@ export const AllTopicsView = (props: Props) => {
<Show when={searchParams().by && searchParams().by !== 'title'}>
<div class="row">
<div class="col-lg-20 col-xl-18">
<div class="col-lg-20 col-xl-18 py-4">
<For each={filteredResults().slice(0, limit())}>
{(topic) => (
<>
<TopicCard
<TopicBadge
topic={topic}
compact={false}
subscribed={subscribed(topic.slug)}
showPublications={true}
showDescription={true}
isFollowed={{
loaded: filteredResults().length > 0,
value: isOwnerSubscribed(topic.slug),
}}
showStat={true}
/>
<div class={styles.stats}>
<span class={styles.statsItem}>
{t('shoutsWithCount', { count: topic.stat.shouts })}
</span>
<span class={styles.statsItem}>
{t('authorsWithCount', { count: topic.stat.authors })}
</span>
<span class={styles.statsItem}>
{t('followersWithCount', { count: topic.stat.followers })}
</span>
</div>
</>
)}
</For>

View File

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

View File

@ -128,7 +128,6 @@ export const AuthorView = (props: Props) => {
const data = await apiClient.getReactionsBy({
by: { comment: false, created_by: commenter.id },
})
console.debug('[components.Author] fetched comments', data)
setCommented(data)
}

View File

@ -20,7 +20,7 @@ interface FollowingContextType {
loadSubscriptions: () => void
follow: (what: FollowingEntity, slug: string) => Promise<void>
unfollow: (what: FollowingEntity, slug: string) => Promise<void>
isOwnerSubscribed: (userId: number) => boolean
isOwnerSubscribed: (id: number | string) => boolean
}
const FollowingContext = createContext<FollowingContextType>()
@ -109,9 +109,11 @@ export const FollowingProvider = (props: { children: JSX.Element }) => {
}
}
const isOwnerSubscribed = (userId: number) => {
if (!author()) return
return !!subscriptions?.authors?.some((authorEntity) => authorEntity.id === userId)
const isOwnerSubscribed = (id?: number | string) => {
if (!author() || !subscriptions) return
const isAuthorSubscribed = subscriptions.authors?.some((authorEntity) => authorEntity.id === id)
const isTopicSubscribed = subscriptions.topics?.some((topicEntity) => topicEntity.slug === id)
return !!isAuthorSubscribed || !!isTopicSubscribed
}
const value: FollowingContextType = {

View File

@ -2,7 +2,7 @@ import type { PageProps } from './types'
import { createSignal, onMount } from 'solid-js'
import { AllTopicsView } from '../components/Views/AllTopics'
import { AllTopics } from '../components/Views/AllTopics'
import { PageLayout } from '../components/_shared/PageLayout'
import { useLocalize } from '../context/localize'
import { loadAllTopics } from '../stores/zine/topics'
@ -23,7 +23,7 @@ export const AllTopicsPage = (props: PageProps) => {
return (
<PageLayout title={t('Themes and plots')}>
<AllTopicsView isLoaded={isLoaded()} topics={props.allTopics} />
<AllTopics isLoaded={isLoaded()} topics={props.allTopics} />
</PageLayout>
)
}

View File

@ -50,4 +50,9 @@ export type UploadedFile = {
originalFilename?: string
}
export type FollowedInfo = {
value?: boolean
loaded?: boolean
}
export type SubscriptionFilter = 'all' | 'authors' | 'topics' | 'communities'