Feature/refactoring subscriptions (#392)

refactoring Author page and Top Authors subscribe logic
This commit is contained in:
Ilya Y 2024-02-03 10:56:11 +03:00 committed by GitHub
parent 829e523d36
commit 21e9fc00da
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 94 additions and 62 deletions

View File

@ -1,6 +1,6 @@
import { openPage } from '@nanostores/router'
import { clsx } from 'clsx'
import { createEffect, createMemo, createSignal, Match, Show, Switch } from 'solid-js'
import { createEffect, createMemo, createSignal, Match, on, Show, Switch } from 'solid-js'
import { useFollowing } from '../../../context/following'
import { useLocalize } from '../../../context/localize'
@ -8,6 +8,7 @@ import { useMediaQuery } from '../../../context/mediaQuery'
import { useSession } from '../../../context/session'
import { Author, FollowingEntity } from '../../../graphql/schema/core.gen'
import { router, useRouter } from '../../../stores/router'
import { resetSortedArticles } from '../../../stores/zine/articles'
import { isCyrillic } from '../../../utils/cyrillic'
import { translit } from '../../../utils/ru2en'
import { Button } from '../../_shared/Button'
@ -19,6 +20,10 @@ import { Userpic } from '../Userpic'
import styles from './AuthorBadge.module.scss'
import stylesButton from '../../_shared/Button/Button.module.scss'
type FollowedInfo = {
value?: boolean
loaded?: boolean
}
type Props = {
author: Author
minimizeSubscribeButton?: boolean
@ -28,20 +33,22 @@ type Props = {
inviteView?: boolean
onInvite?: (id: number) => void
selected?: boolean
isFollowed?: FollowedInfo
}
export const AuthorBadge = (props: Props) => {
const { mediaMatches } = useMediaQuery()
const {
author,
actions: { requireAuthentication },
} = useSession()
const [isMobileView, setIsMobileView] = createSignal(false)
const [followed, setFollowed] = createSignal(false)
const [isFollowed, setIsFollowed] = createSignal<boolean>()
createEffect(() => {
setIsMobileView(!mediaMatches.sm)
})
const {
author,
actions: { requireAuthentication },
} = useSession()
const { setFollowing } = useFollowing()
const { changeSearchParams } = useRouter()
const { t, formatDate, lang } = useLocalize()
@ -68,10 +75,20 @@ export const AuthorBadge = (props: Props) => {
return props.author.name
})
createEffect(
on(
() => props.isFollowed,
() => {
setIsFollowed(props.isFollowed.value)
},
{ defer: true },
),
)
const handleFollowClick = () => {
const value = !followed()
const value = !isFollowed()
requireAuthentication(() => {
setFollowed(value)
setIsFollowed(value)
setFollowing(FollowingEntity.Author, props.author.slug, value)
}, 'subscribe')
}
@ -123,10 +140,10 @@ export const AuthorBadge = (props: Props) => {
<div class={styles.actions}>
<Show
when={!props.minimizeSubscribeButton}
fallback={<CheckButton text={t('Follow')} checked={followed()} onClick={handleFollowClick} />}
fallback={<CheckButton text={t('Follow')} checked={isFollowed()} onClick={handleFollowClick} />}
>
<Show
when={followed()}
when={isFollowed()}
fallback={
<Button
variant={props.iconButtons ? 'secondary' : 'bordered'}
@ -140,7 +157,7 @@ export const AuthorBadge = (props: Props) => {
isSubscribeButton={true}
class={clsx(styles.actionButton, {
[styles.iconed]: props.iconButtons,
[stylesButton.subscribed]: followed(),
[stylesButton.subscribed]: isFollowed(),
})}
/>
}
@ -165,7 +182,7 @@ export const AuthorBadge = (props: Props) => {
isSubscribeButton={true}
class={clsx(styles.actionButton, {
[styles.iconed]: props.iconButtons,
[stylesButton.subscribed]: followed(),
[stylesButton.subscribed]: isFollowed(),
})}
/>
</Show>

View File

@ -2,7 +2,7 @@ import type { Author, Community } from '../../../graphql/schema/core.gen'
import { openPage, redirectPage } from '@nanostores/router'
import { clsx } from 'clsx'
import { createEffect, createMemo, createSignal, For, onMount, Show } from 'solid-js'
import { createEffect, createMemo, createSignal, For, on, onMount, Show } from 'solid-js'
import { useFollowing } from '../../../context/following'
import { useLocalize } from '../../../context/localize'
@ -36,11 +36,24 @@ export const AuthorCard = (props: Props) => {
isSessionLoaded,
actions: { requireAuthentication },
} = useSession()
const [authorSubs, setAuthorSubs] = createSignal<Array<Author | Topic | Community>>([])
const [subscriptionFilter, setSubscriptionFilter] = createSignal<SubscriptionFilter>('all')
const [isFollowed, setIsFollowed] = createSignal<boolean>()
const isProfileOwner = createMemo(() => author()?.slug === props.author.slug)
const [followed, setFollowed] = createSignal()
const isSubscribed = () => props.followers?.some((entity) => entity.id === author()?.id)
createEffect(
on(
() => props.followers,
() => {
setIsFollowed(isSubscribed())
},
{ defer: true },
),
)
const { setFollowing } = useFollowing()
const name = createMemo(() => {
if (lang() !== 'ru' && isCyrillic(props.author.name)) {
if (props.author.name === 'Дискурс') {
@ -82,15 +95,15 @@ export const AuthorCard = (props: Props) => {
})
const handleFollowClick = () => {
const value = !followed()
const value = !isFollowed()
requireAuthentication(() => {
setFollowed(value)
setIsFollowed(value)
setFollowing(FollowingEntity.Author, props.author.slug, value)
}, 'subscribe')
}
const followButtonText = createMemo(() => {
if (followed()) {
if (isFollowed()) {
return (
<>
<span class={stylesButton.buttonSubscribeLabel}>{t('Following')}</span>
@ -198,14 +211,16 @@ export const AuthorCard = (props: Props) => {
when={isProfileOwner()}
fallback={
<div class={styles.authorActions}>
<Show when={isFollowed()}>
<Button
onClick={handleFollowClick}
value={followButtonText()}
isSubscribeButton={true}
class={clsx({
[stylesButton.subscribed]: followed(),
[stylesButton.subscribed]: isFollowed(),
})}
/>
</Show>
<Button
variant={'secondary'}
value={t('Message')}

View File

@ -3,8 +3,9 @@
import type { Author, Shout, Topic } from '../../graphql/schema/core.gen'
import { clsx } from 'clsx'
import { For, Show } from 'solid-js'
import { createEffect, createSignal, For, Show } from 'solid-js'
import { useFollowing } from '../../context/following'
import { useLocalize } from '../../context/localize'
import { Icon } from '../_shared/Icon'
import { AuthorBadge } from '../Author/AuthorBadge'
@ -29,6 +30,13 @@ type Props = {
export const Beside = (props: Props) => {
const { t } = useLocalize()
const { subscriptions } = useFollowing()
const [subscriptionsAuthorsId, setSubscriptionsAuthorsId] = createSignal<number[] | undefined>()
createEffect(() => {
setSubscriptionsAuthorsId(subscriptions?.authors?.map((item) => item.id) || [])
})
return (
<Show when={!!props.beside?.slug && props.values?.length > 0}>
<div class="floor floor--9">
@ -83,7 +91,13 @@ export const Beside = (props: Props) => {
/>
</Show>
<Show when={props.wrapper === 'author'}>
<AuthorBadge author={value as Author} />
<AuthorBadge
author={value as Author}
isFollowed={{
loaded: Boolean(subscriptionsAuthorsId()),
value: subscriptionsAuthorsId().includes(value.id),
}}
/>
</Show>
<Show when={props.wrapper === 'article' && value?.slug}>
<ArticleCard

View File

@ -14,7 +14,6 @@ import { loadAuthor, useAuthorsStore } from '../../../stores/zine/authors'
import { getImageUrl } from '../../../utils/getImageUrl'
import { getDescription } from '../../../utils/meta'
import { restoreScrollPosition, saveScrollPosition } from '../../../utils/scroll'
import { byCreated } from '../../../utils/sortby'
import { splitToPages } from '../../../utils/splitToPages'
import { Loading } from '../../_shared/Loading'
import { Comment } from '../../Article/Comment'
@ -32,7 +31,6 @@ type Props = {
author: Author
authorSlug: string
}
export const PRERENDERED_ARTICLES_COUNT = 12
const LOAD_MORE_PAGE_SIZE = 9
@ -69,18 +67,26 @@ export const AuthorView = (props: Props) => {
const bioContainerRef: { current: HTMLDivElement } = { current: null }
const bioWrapperRef: { current: HTMLDivElement } = { current: null }
const fetchSubscriptions = async (): Promise<{ authors: Author[]; topics: Topic[] }> => {
const fetchData = async (slug) => {
try {
const [subscriptionsResult, followersResult] = await Promise.all([
(async () => {
const [getAuthors, getTopics] = await Promise.all([
apiClient.getAuthorFollowingAuthors({ slug: props.authorSlug }),
apiClient.getAuthorFollowingTopics({ slug: props.authorSlug }),
apiClient.getAuthorFollowingAuthors({ slug }),
apiClient.getAuthorFollowingTopics({ slug }),
])
const authors = getAuthors
const topics = getTopics
return { authors, topics }
return { authors: getAuthors, topics: getTopics }
})(),
apiClient.getAuthorFollowers({ slug }),
])
const { authors, topics } = subscriptionsResult
setFollowing([...(authors || []), ...(topics || [])])
setFollowers(followersResult || [])
console.info('[components.Author] following data loaded')
} catch (error) {
console.error('[fetchSubscriptions] :', error)
throw error
console.error('[components.Author] fetch error', error)
}
}
@ -90,28 +96,7 @@ export const AuthorView = (props: Props) => {
}
}
const fetchData = async () => {
const slug = author()?.slug || props.authorSlug
if (slug && getPage().route === 'authorComments' && author()) {
try {
const { authors, topics } = await fetchSubscriptions()
setFollowing([...(authors || []), ...(topics || [])])
const flwrs = await apiClient.getAuthorFollowers({ slug })
setFollowers(flwrs || [])
console.info('[components.Author] following data loaded')
} catch (error) {
console.error('[components.Author] fetch error', error)
}
}
}
createEffect(() => {
if (author()) {
console.info('[components.Author] profile data loaded')
document.title = author().name
fetchData()
}
})
onMount(() => fetchData(props.authorSlug))
const loadMore = async () => {
saveScrollPosition()

View File

@ -1,6 +1,7 @@
import { getPagePath } from '@nanostores/router'
import { batch, createMemo, createSignal, For, onMount, Show } from 'solid-js'
import { batch, createEffect, createMemo, createSignal, For, onMount, Show } from 'solid-js'
import { useFollowing } from '../../context/following'
import { useLocalize } from '../../context/localize'
import { apiClient } from '../../graphql/client/core'
import { Shout, Topic } from '../../graphql/schema/core.gen'

View File

@ -109,7 +109,7 @@ const addArticles = (...args: Shout[][]) => {
}
const addSortedArticles = (articles: Shout[]) => {
setSortedArticles((prevSortedArticles) => [...prevSortedArticles, ...articles])
setSortedArticles((prevSortedArticles) => [...prevSortedArticles, ...(articles || [])])
}
export const loadShout = async (slug: string): Promise<void> => {