Feature/refactoring subscriptions (#392)
refactoring Author page and Top Authors subscribe logic
This commit is contained in:
parent
829e523d36
commit
21e9fc00da
|
@ -1,6 +1,6 @@
|
||||||
import { openPage } from '@nanostores/router'
|
import { openPage } from '@nanostores/router'
|
||||||
import { clsx } from 'clsx'
|
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 { useFollowing } from '../../../context/following'
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
|
@ -8,6 +8,7 @@ import { useMediaQuery } from '../../../context/mediaQuery'
|
||||||
import { useSession } from '../../../context/session'
|
import { useSession } from '../../../context/session'
|
||||||
import { Author, FollowingEntity } from '../../../graphql/schema/core.gen'
|
import { Author, FollowingEntity } from '../../../graphql/schema/core.gen'
|
||||||
import { router, useRouter } from '../../../stores/router'
|
import { router, useRouter } from '../../../stores/router'
|
||||||
|
import { resetSortedArticles } from '../../../stores/zine/articles'
|
||||||
import { isCyrillic } from '../../../utils/cyrillic'
|
import { isCyrillic } from '../../../utils/cyrillic'
|
||||||
import { translit } from '../../../utils/ru2en'
|
import { translit } from '../../../utils/ru2en'
|
||||||
import { Button } from '../../_shared/Button'
|
import { Button } from '../../_shared/Button'
|
||||||
|
@ -19,6 +20,10 @@ import { Userpic } from '../Userpic'
|
||||||
import styles from './AuthorBadge.module.scss'
|
import styles from './AuthorBadge.module.scss'
|
||||||
import stylesButton from '../../_shared/Button/Button.module.scss'
|
import stylesButton from '../../_shared/Button/Button.module.scss'
|
||||||
|
|
||||||
|
type FollowedInfo = {
|
||||||
|
value?: boolean
|
||||||
|
loaded?: boolean
|
||||||
|
}
|
||||||
type Props = {
|
type Props = {
|
||||||
author: Author
|
author: Author
|
||||||
minimizeSubscribeButton?: boolean
|
minimizeSubscribeButton?: boolean
|
||||||
|
@ -28,20 +33,22 @@ type Props = {
|
||||||
inviteView?: boolean
|
inviteView?: boolean
|
||||||
onInvite?: (id: number) => void
|
onInvite?: (id: number) => void
|
||||||
selected?: boolean
|
selected?: boolean
|
||||||
|
isFollowed?: FollowedInfo
|
||||||
}
|
}
|
||||||
export const AuthorBadge = (props: Props) => {
|
export const AuthorBadge = (props: Props) => {
|
||||||
const { mediaMatches } = useMediaQuery()
|
const { mediaMatches } = useMediaQuery()
|
||||||
|
const {
|
||||||
|
author,
|
||||||
|
actions: { requireAuthentication },
|
||||||
|
} = useSession()
|
||||||
|
|
||||||
const [isMobileView, setIsMobileView] = createSignal(false)
|
const [isMobileView, setIsMobileView] = createSignal(false)
|
||||||
const [followed, setFollowed] = createSignal(false)
|
const [isFollowed, setIsFollowed] = createSignal<boolean>()
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
setIsMobileView(!mediaMatches.sm)
|
setIsMobileView(!mediaMatches.sm)
|
||||||
})
|
})
|
||||||
|
|
||||||
const {
|
|
||||||
author,
|
|
||||||
actions: { requireAuthentication },
|
|
||||||
} = useSession()
|
|
||||||
const { setFollowing } = useFollowing()
|
const { setFollowing } = useFollowing()
|
||||||
const { changeSearchParams } = useRouter()
|
const { changeSearchParams } = useRouter()
|
||||||
const { t, formatDate, lang } = useLocalize()
|
const { t, formatDate, lang } = useLocalize()
|
||||||
|
@ -68,10 +75,20 @@ export const AuthorBadge = (props: Props) => {
|
||||||
return props.author.name
|
return props.author.name
|
||||||
})
|
})
|
||||||
|
|
||||||
|
createEffect(
|
||||||
|
on(
|
||||||
|
() => props.isFollowed,
|
||||||
|
() => {
|
||||||
|
setIsFollowed(props.isFollowed.value)
|
||||||
|
},
|
||||||
|
{ defer: true },
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
const handleFollowClick = () => {
|
const handleFollowClick = () => {
|
||||||
const value = !followed()
|
const value = !isFollowed()
|
||||||
requireAuthentication(() => {
|
requireAuthentication(() => {
|
||||||
setFollowed(value)
|
setIsFollowed(value)
|
||||||
setFollowing(FollowingEntity.Author, props.author.slug, value)
|
setFollowing(FollowingEntity.Author, props.author.slug, value)
|
||||||
}, 'subscribe')
|
}, 'subscribe')
|
||||||
}
|
}
|
||||||
|
@ -123,10 +140,10 @@ export const AuthorBadge = (props: Props) => {
|
||||||
<div class={styles.actions}>
|
<div class={styles.actions}>
|
||||||
<Show
|
<Show
|
||||||
when={!props.minimizeSubscribeButton}
|
when={!props.minimizeSubscribeButton}
|
||||||
fallback={<CheckButton text={t('Follow')} checked={followed()} onClick={handleFollowClick} />}
|
fallback={<CheckButton text={t('Follow')} checked={isFollowed()} onClick={handleFollowClick} />}
|
||||||
>
|
>
|
||||||
<Show
|
<Show
|
||||||
when={followed()}
|
when={isFollowed()}
|
||||||
fallback={
|
fallback={
|
||||||
<Button
|
<Button
|
||||||
variant={props.iconButtons ? 'secondary' : 'bordered'}
|
variant={props.iconButtons ? 'secondary' : 'bordered'}
|
||||||
|
@ -140,7 +157,7 @@ export const AuthorBadge = (props: Props) => {
|
||||||
isSubscribeButton={true}
|
isSubscribeButton={true}
|
||||||
class={clsx(styles.actionButton, {
|
class={clsx(styles.actionButton, {
|
||||||
[styles.iconed]: props.iconButtons,
|
[styles.iconed]: props.iconButtons,
|
||||||
[stylesButton.subscribed]: followed(),
|
[stylesButton.subscribed]: isFollowed(),
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
@ -165,7 +182,7 @@ export const AuthorBadge = (props: Props) => {
|
||||||
isSubscribeButton={true}
|
isSubscribeButton={true}
|
||||||
class={clsx(styles.actionButton, {
|
class={clsx(styles.actionButton, {
|
||||||
[styles.iconed]: props.iconButtons,
|
[styles.iconed]: props.iconButtons,
|
||||||
[stylesButton.subscribed]: followed(),
|
[stylesButton.subscribed]: isFollowed(),
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
|
@ -2,7 +2,7 @@ import type { Author, Community } from '../../../graphql/schema/core.gen'
|
||||||
|
|
||||||
import { openPage, redirectPage } from '@nanostores/router'
|
import { openPage, redirectPage } from '@nanostores/router'
|
||||||
import { clsx } from 'clsx'
|
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 { useFollowing } from '../../../context/following'
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
|
@ -36,11 +36,24 @@ export const AuthorCard = (props: Props) => {
|
||||||
isSessionLoaded,
|
isSessionLoaded,
|
||||||
actions: { requireAuthentication },
|
actions: { requireAuthentication },
|
||||||
} = useSession()
|
} = useSession()
|
||||||
|
|
||||||
const [authorSubs, setAuthorSubs] = createSignal<Array<Author | Topic | Community>>([])
|
const [authorSubs, setAuthorSubs] = createSignal<Array<Author | Topic | Community>>([])
|
||||||
const [subscriptionFilter, setSubscriptionFilter] = createSignal<SubscriptionFilter>('all')
|
const [subscriptionFilter, setSubscriptionFilter] = createSignal<SubscriptionFilter>('all')
|
||||||
|
const [isFollowed, setIsFollowed] = createSignal<boolean>()
|
||||||
const isProfileOwner = createMemo(() => author()?.slug === props.author.slug)
|
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 { setFollowing } = useFollowing()
|
||||||
|
|
||||||
const name = createMemo(() => {
|
const name = createMemo(() => {
|
||||||
if (lang() !== 'ru' && isCyrillic(props.author.name)) {
|
if (lang() !== 'ru' && isCyrillic(props.author.name)) {
|
||||||
if (props.author.name === 'Дискурс') {
|
if (props.author.name === 'Дискурс') {
|
||||||
|
@ -82,15 +95,15 @@ export const AuthorCard = (props: Props) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleFollowClick = () => {
|
const handleFollowClick = () => {
|
||||||
const value = !followed()
|
const value = !isFollowed()
|
||||||
requireAuthentication(() => {
|
requireAuthentication(() => {
|
||||||
setFollowed(value)
|
setIsFollowed(value)
|
||||||
setFollowing(FollowingEntity.Author, props.author.slug, value)
|
setFollowing(FollowingEntity.Author, props.author.slug, value)
|
||||||
}, 'subscribe')
|
}, 'subscribe')
|
||||||
}
|
}
|
||||||
|
|
||||||
const followButtonText = createMemo(() => {
|
const followButtonText = createMemo(() => {
|
||||||
if (followed()) {
|
if (isFollowed()) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<span class={stylesButton.buttonSubscribeLabel}>{t('Following')}</span>
|
<span class={stylesButton.buttonSubscribeLabel}>{t('Following')}</span>
|
||||||
|
@ -198,14 +211,16 @@ export const AuthorCard = (props: Props) => {
|
||||||
when={isProfileOwner()}
|
when={isProfileOwner()}
|
||||||
fallback={
|
fallback={
|
||||||
<div class={styles.authorActions}>
|
<div class={styles.authorActions}>
|
||||||
<Button
|
<Show when={isFollowed()}>
|
||||||
onClick={handleFollowClick}
|
<Button
|
||||||
value={followButtonText()}
|
onClick={handleFollowClick}
|
||||||
isSubscribeButton={true}
|
value={followButtonText()}
|
||||||
class={clsx({
|
isSubscribeButton={true}
|
||||||
[stylesButton.subscribed]: followed(),
|
class={clsx({
|
||||||
})}
|
[stylesButton.subscribed]: isFollowed(),
|
||||||
/>
|
})}
|
||||||
|
/>
|
||||||
|
</Show>
|
||||||
<Button
|
<Button
|
||||||
variant={'secondary'}
|
variant={'secondary'}
|
||||||
value={t('Message')}
|
value={t('Message')}
|
||||||
|
|
|
@ -3,8 +3,9 @@
|
||||||
import type { Author, Shout, Topic } from '../../graphql/schema/core.gen'
|
import type { Author, Shout, Topic } from '../../graphql/schema/core.gen'
|
||||||
|
|
||||||
import { clsx } from 'clsx'
|
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 { useLocalize } from '../../context/localize'
|
||||||
import { Icon } from '../_shared/Icon'
|
import { Icon } from '../_shared/Icon'
|
||||||
import { AuthorBadge } from '../Author/AuthorBadge'
|
import { AuthorBadge } from '../Author/AuthorBadge'
|
||||||
|
@ -29,6 +30,13 @@ type Props = {
|
||||||
|
|
||||||
export const Beside = (props: Props) => {
|
export const Beside = (props: Props) => {
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
|
const { subscriptions } = useFollowing()
|
||||||
|
const [subscriptionsAuthorsId, setSubscriptionsAuthorsId] = createSignal<number[] | undefined>()
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
setSubscriptionsAuthorsId(subscriptions?.authors?.map((item) => item.id) || [])
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Show when={!!props.beside?.slug && props.values?.length > 0}>
|
<Show when={!!props.beside?.slug && props.values?.length > 0}>
|
||||||
<div class="floor floor--9">
|
<div class="floor floor--9">
|
||||||
|
@ -83,7 +91,13 @@ export const Beside = (props: Props) => {
|
||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={props.wrapper === 'author'}>
|
<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>
|
||||||
<Show when={props.wrapper === 'article' && value?.slug}>
|
<Show when={props.wrapper === 'article' && value?.slug}>
|
||||||
<ArticleCard
|
<ArticleCard
|
||||||
|
|
|
@ -14,7 +14,6 @@ import { loadAuthor, useAuthorsStore } from '../../../stores/zine/authors'
|
||||||
import { getImageUrl } from '../../../utils/getImageUrl'
|
import { getImageUrl } from '../../../utils/getImageUrl'
|
||||||
import { getDescription } from '../../../utils/meta'
|
import { getDescription } from '../../../utils/meta'
|
||||||
import { restoreScrollPosition, saveScrollPosition } from '../../../utils/scroll'
|
import { restoreScrollPosition, saveScrollPosition } from '../../../utils/scroll'
|
||||||
import { byCreated } from '../../../utils/sortby'
|
|
||||||
import { splitToPages } from '../../../utils/splitToPages'
|
import { splitToPages } from '../../../utils/splitToPages'
|
||||||
import { Loading } from '../../_shared/Loading'
|
import { Loading } from '../../_shared/Loading'
|
||||||
import { Comment } from '../../Article/Comment'
|
import { Comment } from '../../Article/Comment'
|
||||||
|
@ -32,7 +31,6 @@ type Props = {
|
||||||
author: Author
|
author: Author
|
||||||
authorSlug: string
|
authorSlug: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PRERENDERED_ARTICLES_COUNT = 12
|
export const PRERENDERED_ARTICLES_COUNT = 12
|
||||||
const LOAD_MORE_PAGE_SIZE = 9
|
const LOAD_MORE_PAGE_SIZE = 9
|
||||||
|
|
||||||
|
@ -69,18 +67,26 @@ export const AuthorView = (props: Props) => {
|
||||||
const bioContainerRef: { current: HTMLDivElement } = { current: null }
|
const bioContainerRef: { current: HTMLDivElement } = { current: null }
|
||||||
const bioWrapperRef: { current: HTMLDivElement } = { current: null }
|
const bioWrapperRef: { current: HTMLDivElement } = { current: null }
|
||||||
|
|
||||||
const fetchSubscriptions = async (): Promise<{ authors: Author[]; topics: Topic[] }> => {
|
const fetchData = async (slug) => {
|
||||||
try {
|
try {
|
||||||
const [getAuthors, getTopics] = await Promise.all([
|
const [subscriptionsResult, followersResult] = await Promise.all([
|
||||||
apiClient.getAuthorFollowingAuthors({ slug: props.authorSlug }),
|
(async () => {
|
||||||
apiClient.getAuthorFollowingTopics({ slug: props.authorSlug }),
|
const [getAuthors, getTopics] = await Promise.all([
|
||||||
|
apiClient.getAuthorFollowingAuthors({ slug }),
|
||||||
|
apiClient.getAuthorFollowingTopics({ slug }),
|
||||||
|
])
|
||||||
|
return { authors: getAuthors, topics: getTopics }
|
||||||
|
})(),
|
||||||
|
apiClient.getAuthorFollowers({ slug }),
|
||||||
])
|
])
|
||||||
const authors = getAuthors
|
|
||||||
const topics = getTopics
|
const { authors, topics } = subscriptionsResult
|
||||||
return { authors, topics }
|
setFollowing([...(authors || []), ...(topics || [])])
|
||||||
|
setFollowers(followersResult || [])
|
||||||
|
|
||||||
|
console.info('[components.Author] following data loaded')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[fetchSubscriptions] :', error)
|
console.error('[components.Author] fetch error', error)
|
||||||
throw error
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,28 +96,7 @@ export const AuthorView = (props: Props) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchData = async () => {
|
onMount(() => fetchData(props.authorSlug))
|
||||||
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()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const loadMore = async () => {
|
const loadMore = async () => {
|
||||||
saveScrollPosition()
|
saveScrollPosition()
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { getPagePath } from '@nanostores/router'
|
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 { useLocalize } from '../../context/localize'
|
||||||
import { apiClient } from '../../graphql/client/core'
|
import { apiClient } from '../../graphql/client/core'
|
||||||
import { Shout, Topic } from '../../graphql/schema/core.gen'
|
import { Shout, Topic } from '../../graphql/schema/core.gen'
|
||||||
|
|
|
@ -109,7 +109,7 @@ const addArticles = (...args: Shout[][]) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const addSortedArticles = (articles: Shout[]) => {
|
const addSortedArticles = (articles: Shout[]) => {
|
||||||
setSortedArticles((prevSortedArticles) => [...prevSortedArticles, ...articles])
|
setSortedArticles((prevSortedArticles) => [...prevSortedArticles, ...(articles || [])])
|
||||||
}
|
}
|
||||||
|
|
||||||
export const loadShout = async (slug: string): Promise<void> => {
|
export const loadShout = async (slug: string): Promise<void> => {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user