From f4f4e8081678a8a9e94d20865e750b37d764f8f8 Mon Sep 17 00:00:00 2001 From: Untone Date: Sun, 7 Jul 2024 16:48:53 +0300 Subject: [PATCH] authors-all-fix+slug-404 --- src/components/Article/FullArticle.tsx | 86 +++++++++---------- src/components/AuthorsList/AuthorsList.tsx | 29 ++----- .../Editor/AudioUploader/AudioUploader.tsx | 3 +- .../Feed/ArticleCard/ArticleCard.tsx | 2 +- src/components/Nav/Header/Header.tsx | 2 +- .../Views/AllAuthors/AllAuthors.tsx | 29 ++++--- src/context/authors.tsx | 4 +- src/context/reactions.tsx | 8 +- src/graphql/api/public.ts | 2 + src/graphql/query/core/authors-all.ts | 2 +- src/graphql/query/core/authors-load-by.ts | 4 +- src/routes/*404.tsx | 3 +- src/routes/[slug].tsx | 85 ++++++++++-------- src/routes/author/(all-authors).tsx | 54 +++++++++--- 14 files changed, 179 insertions(+), 134 deletions(-) diff --git a/src/components/Article/FullArticle.tsx b/src/components/Article/FullArticle.tsx index 61eb5d09..17891d64 100644 --- a/src/components/Article/FullArticle.tsx +++ b/src/components/Article/FullArticle.tsx @@ -1,15 +1,16 @@ -import { createPopper } from '@popperjs/core' -import { clsx } from 'clsx' // import { install } from 'ga-gtag' +import { createPopper } from '@popperjs/core' +import { Link, Meta } from '@solidjs/meta' +import { A, useSearchParams } from '@solidjs/router' +import { clsx } from 'clsx' import { For, Show, createEffect, createMemo, createSignal, on, onCleanup, onMount } from 'solid-js' import { isServer } from 'solid-js/web' - -import { Link, Meta } from '@solidjs/meta' +import { useFeed } from '~/context/feed' import { useLocalize } from '~/context/localize' import { useReactions } from '~/context/reactions' import { useSession } from '~/context/session' import { DEFAULT_HEADER_OFFSET, useUI } from '~/context/ui' -import type { Author, Maybe, Shout, Topic } from '~/graphql/schema/core.gen' +import type { Author, Maybe, QueryLoad_Reactions_ByArgs, Shout, Topic } from '~/graphql/schema/core.gen' import { isCyrillic } from '~/intl/translate' import { getImageUrl, getOpenGraphImageUrl } from '~/lib/getImageUrl' import { MediaItem } from '~/types/mediaitem' @@ -34,8 +35,6 @@ import { CommentsTree } from './CommentsTree' import { SharePopup, getShareUrl } from './SharePopup' import { ShoutRatingControl } from './ShoutRatingControl' -import { A, useSearchParams } from '@solidjs/router' -import { useFeed } from '~/context/feed' import stylesHeader from '../Nav/Header/Header.module.scss' import styles from './Article.module.scss' @@ -79,24 +78,24 @@ export const FullArticle = (props: Props) => { const author = createMemo(() => session()?.user?.app_data?.profile as Author) const { addSeen } = useFeed() - const formattedDate = createMemo(() => formatDate(new Date((props.article?.published_at || 0) * 1000))) + const formattedDate = createMemo(() => formatDate(new Date((props.article.published_at || 0) * 1000))) const canEdit = createMemo( () => Boolean(author()?.id) && - (props.article?.authors?.some((a) => Boolean(a) && a?.id === author().id) || - props.article?.created_by?.id === author().id || + (props.article.authors?.some((a) => Boolean(a) && a?.id === author().id) || + props.article.created_by?.id === author().id || session()?.user?.roles?.includes('editor')) ) const mainTopic = createMemo(() => { - const mainTopicSlug = (props.article?.topics?.length || 0) > 0 ? props.article.main_topic : null - const mt = props.article?.topics?.find((tpc: Maybe) => tpc?.slug === mainTopicSlug) + const mainTopicSlug = (props.article.topics?.length || 0) > 0 ? props.article.main_topic : null + const mt = props.article.topics?.find((tpc: Maybe) => tpc?.slug === mainTopicSlug) if (mt) { mt.title = lang() === 'en' ? capitalize(mt.slug.replace(/-/, ' ')) : mt.title return mt } - return props.article?.topics?.[0] + return props.article.topics?.[0] }) const handleBookmarkButtonClick = (ev: MouseEvent | undefined) => { @@ -107,10 +106,10 @@ export const FullArticle = (props: Props) => { } const body = createMemo(() => { - if (props.article?.layout === 'literature') { + if (props.article.layout === 'literature') { try { - if (props.article?.media) { - const media = JSON.parse(props.article?.media) + if (props.article.media) { + const media = JSON.parse(props.article.media) if (media.length > 0) { return media[0].body } @@ -119,7 +118,7 @@ export const FullArticle = (props: Props) => { console.error(error) } } - return props.article?.body || '' + return props.article.body || '' }) const imageUrls = createMemo(() => { @@ -145,7 +144,7 @@ export const FullArticle = (props: Props) => { const media = createMemo(() => { try { - return JSON.parse(props.article?.media || '[]') + return JSON.parse(props.article.media || '[]') } catch { return [] } @@ -304,7 +303,8 @@ export const FullArticle = (props: Props) => { onMount(async () => { // install('G-LQ4B87H8C2') - await loadReactionsBy({ by: { shout: props.article.slug } }) + const opts: QueryLoad_Reactions_ByArgs = { by: { shout: props.article.slug }, limit: 999, offset: 0 } + const _rrr = await loadReactionsBy(opts) addSeen(props.article.slug) setIsReactionsLoaded(true) document.title = props.article.title @@ -326,18 +326,18 @@ export const FullArticle = (props: Props) => { }) }) - const cover = props.article.cover ?? 'production/image/logo_image.png' + const cover = props.article.cover || 'production/image/logo_image.png' const ogImage = getOpenGraphImageUrl(cover, { title: props.article.title, topic: mainTopic()?.title || '', - author: props.article?.authors?.[0]?.name || '', + author: props.article.authors?.[0]?.name || '', width: 1200 }) const description = getArticleDescription(props.article.description || body() || media()[0]?.body) const ogTitle = props.article.title const keywords = getArticleKeywords(props.article) - const shareUrl = getShareUrl({ pathname: `/${props.article.slug}` }) + const shareUrl = getShareUrl({ pathname: `/${props.article.slug || ''}` }) const getAuthorName = (a: Author) => { return lang() === 'en' && isCyrillic(a.name || '') ? capitalize(a.slug.replace(/-/, ' ')) : a.name } @@ -363,19 +363,19 @@ export const FullArticle = (props: Props) => { onClick={handleArticleBodyClick} > {/*TODO: Check styles.shoutTopic*/} - +
-

{props.article?.title || ''}

- -

{props.article?.subtitle || ''}

+

{props.article.title || ''}

+ +

{props.article.subtitle || ''}

- + {(a: Maybe, index: () => number) => ( <> 0}>, @@ -386,39 +386,39 @@ export const FullArticle = (props: Props) => {
{props.article?.cover_caption -
+
- -
+ +
- + 0}>
- +
- +
{(m: MediaItem) => ( @@ -542,7 +542,7 @@ export const FullArticle = (props: Props) => { {(triggerRef: (el: HTMLElement) => void) => (
- + @@ -577,9 +577,9 @@ export const FullArticle = (props: Props) => {
- +
- + {(topic) => (
diff --git a/src/components/AuthorsList/AuthorsList.tsx b/src/components/AuthorsList/AuthorsList.tsx index be9ee978..95305042 100644 --- a/src/components/AuthorsList/AuthorsList.tsx +++ b/src/components/AuthorsList/AuthorsList.tsx @@ -1,12 +1,12 @@ import { clsx } from 'clsx' import { For, Show, createEffect, createSignal, on } from 'solid-js' import { useAuthors } from '~/context/authors' -import { useGraphQL } from '~/context/graphql' import { useLocalize } from '~/context/localize' -import loadAuthorsByQuery from '~/graphql/query/core/authors-load-by' +import { loadAuthors } from '~/graphql/api/public' import { Author } from '~/graphql/schema/core.gen' import { AuthorBadge } from '../Author/AuthorBadge' import { InlineLoader } from '../InlineLoader' +import { AUTHORS_PER_PAGE } from '../Views/AllAuthors/AllAuthors' import { Button } from '../_shared/Button' import styles from './AuthorsList.module.scss' @@ -17,7 +17,7 @@ type Props = { allAuthorsLength?: number } -const PAGE_SIZE = 20 +// pagination handling, loadAuthors cached from api, addAuthors to context export const AuthorsList = (props: Props) => { const { t } = useLocalize() @@ -27,18 +27,17 @@ export const AuthorsList = (props: Props) => { const [loading, setLoading] = createSignal(false) const [currentPage, setCurrentPage] = createSignal({ shouts: 0, followers: 0 }) const [allLoaded, setAllLoaded] = createSignal(false) - const { query } = useGraphQL() const fetchAuthors = async (queryType: Props['query'], page: number) => { setLoading(true) - const offset = PAGE_SIZE * page - const resp = await query(loadAuthorsByQuery, { + const offset = AUTHORS_PER_PAGE * page + const fetcher = await loadAuthors({ by: { order: queryType }, - limit: PAGE_SIZE, + limit: AUTHORS_PER_PAGE, offset }) - const result = resp?.data?.load_authors_by - if ((result?.length || 0) > 0) { + const result = await fetcher() + if (result) { addAuthors([...result]) if (queryType === 'shouts') { setAuthorsByShouts((prev) => [...(prev || []), ...result]) @@ -70,17 +69,7 @@ export const AuthorsList = (props: Props) => { ) const authorsList = () => (props.query === 'shouts' ? authorsByShouts() : authorsByFollowers()) - - // TODO: do it with backend - // createEffect(() => { - // if (props.searchQuery) { - // // search logic - // } - // }) - - createEffect(() => { - setAllLoaded(props.allAuthorsLength === authorsList.length) - }) + createEffect(() => setAllLoaded(props.allAuthorsLength === authorsList.length)) return (
diff --git a/src/components/Editor/AudioUploader/AudioUploader.tsx b/src/components/Editor/AudioUploader/AudioUploader.tsx index ee6c57f5..c5e0e4f1 100644 --- a/src/components/Editor/AudioUploader/AudioUploader.tsx +++ b/src/components/Editor/AudioUploader/AudioUploader.tsx @@ -8,9 +8,8 @@ import { composeMediaItems } from '~/utils/composeMediaItems' import { AudioPlayer } from '../../Article/AudioPlayer' import styles from './AudioUploader.module.scss' - if (!isServer && window) window.Buffer = Buffer -console.debug('buffer patch passed') +// console.debug('buffer patch passed') type Props = { class?: string diff --git a/src/components/Feed/ArticleCard/ArticleCard.tsx b/src/components/Feed/ArticleCard/ArticleCard.tsx index d4eb7ce2..cad0004f 100644 --- a/src/components/Feed/ArticleCard/ArticleCard.tsx +++ b/src/components/Feed/ArticleCard/ArticleCard.tsx @@ -220,7 +220,7 @@ export const ArticleCard = (props: ArticleCardProps) => { [styles.shoutCardTitlesContainerFeedMode]: props.settings?.isFeedMode })} > - +
diff --git a/src/components/Nav/Header/Header.tsx b/src/components/Nav/Header/Header.tsx index 4c72f414..776dabbd 100644 --- a/src/components/Nav/Header/Header.tsx +++ b/src/components/Nav/Header/Header.tsx @@ -191,7 +191,7 @@ export const Header = (props: Props) => {
{props.title}
-
    !fixed() && toggleFixed()}> +
      toggleSubnavigation(true, setIsZineVisible)} onMouseOut={hideSubnavigation} diff --git a/src/components/Views/AllAuthors/AllAuthors.tsx b/src/components/Views/AllAuthors/AllAuthors.tsx index 7f187f7d..bca831e2 100644 --- a/src/components/Views/AllAuthors/AllAuthors.tsx +++ b/src/components/Views/AllAuthors/AllAuthors.tsx @@ -10,6 +10,7 @@ import type { Author } from '~/graphql/schema/core.gen' import enKeywords from '~/intl/locales/en/keywords.json' import ruKeywords from '~/intl/locales/ru/keywords.json' import { authorLetterReduce, translateAuthor } from '~/intl/translate' +import { dummyFilter } from '~/lib/dummyFilter' import { getImageUrl } from '~/lib/getImageUrl' import { scrollHandler } from '~/utils/scroll' import { AuthorsList } from '../../AuthorsList' @@ -27,23 +28,29 @@ export const ABC = { en: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ#' } +// useAuthors sorted from context, set filter/sort + export const AllAuthors = (props: Props) => { const { t, lang } = useLocalize() - const [searchQuery, setSearchQuery] = createSignal('') const alphabet = createMemo(() => ABC[lang()] || ABC['ru']) const [searchParams, changeSearchParams] = useSearchParams<{ by?: string }>() - const { authorsSorted, addAuthors, setAuthorsSort } = useAuthors() + const { authorsSorted, setAuthorsSort } = useAuthors() + const authors = createMemo(() => props.authors || authorsSorted()) + // filter + const [searchQuery, setSearchQuery] = createSignal('') + const [filteredAuthors, setFilteredAuthors] = createSignal([]) + createEffect(() => + authors() && setFilteredAuthors((_prev: Author[]) => dummyFilter(authors(), searchQuery(), lang()) as Author[]) + ) + + // sort by onMount(() => !searchParams?.by && changeSearchParams({ by: 'name' })) createEffect(on(() => searchParams?.by || 'name', setAuthorsSort || ((_) => null), {})) - createEffect(on(() => props.authors || [], addAuthors || ((_) => null), {})) - - const filteredAuthors = createMemo(() => { - const query = searchQuery().toLowerCase() - return authorsSorted?.()?.filter((a: Author) => a?.name?.toLowerCase().includes(query)) || [] - }) + // store by first char const byLetterFiltered = createMemo<{ [letter: string]: Author[] }>(() => { + console.debug('[components.AllAuthors] byLetterFiltered') return ( filteredAuthors()?.reduce( (acc, author: Author) => authorLetterReduce(acc, author, lang()), @@ -65,7 +72,7 @@ export const AllAuthors = (props: Props) => { const description = createMemo(() => t('List of authors of the open editorial community')) return ( -
      +
      @@ -165,9 +172,9 @@ export const AllAuthors = (props: Props) => { )} - + diff --git a/src/context/authors.tsx b/src/context/authors.tsx index e2a6fec8..901b20a7 100644 --- a/src/context/authors.tsx +++ b/src/context/authors.tsx @@ -173,13 +173,13 @@ export const AuthorsProvider = (props: { children: JSX.Element }) => { } const contextValue: AuthorsContextType = { - loadAllAuthors, authorsEntities, authorsSorted, addAuthors, addAuthor, loadAuthor, - loadAuthors: loadAuthorsPaginated, + loadAuthors: loadAuthorsPaginated, // with stat + loadAllAuthors, // without stat topAuthors, authorsByTopic, setAuthorsSort diff --git a/src/context/reactions.tsx b/src/context/reactions.tsx index 8afb26d4..320c7b48 100644 --- a/src/context/reactions.tsx +++ b/src/context/reactions.tsx @@ -2,10 +2,10 @@ import type { JSX } from 'solid-js' import { createContext, onCleanup, useContext } from 'solid-js' import { createStore, reconcile } from 'solid-js/store' +import { loadReactions } from '~/graphql/api/public' import createReactionMutation from '~/graphql/mutation/core/reaction-create' import destroyReactionMutation from '~/graphql/mutation/core/reaction-destroy' import updateReactionMutation from '~/graphql/mutation/core/reaction-update' -import getReactionsByQuery from '~/graphql/query/core/reactions-load-by' import { MutationCreate_ReactionArgs, MutationUpdate_ReactionArgs, @@ -37,11 +37,11 @@ export const ReactionsProvider = (props: { children: JSX.Element }) => { const [reactionsByShout, setReactionsByShout] = createStore>({}) const { t } = useLocalize() const { showSnackbar } = useSnackbar() - const { query, mutation } = useGraphQL() + const { mutation } = useGraphQL() const loadReactionsBy = async (opts: QueryLoad_Reactions_ByArgs): Promise => { - const resp = await query(getReactionsByQuery, opts) - const result = resp?.data?.load_reactions_by || [] + const fetcher = await loadReactions({ ...opts }) + const result = (await fetcher()) || [] const newReactionsByShout: Record = {} const newReactionEntities = result.reduce( (acc: { [reaction_id: number]: Reaction }, reaction: Reaction) => { diff --git a/src/graphql/api/public.ts b/src/graphql/api/public.ts index 8fdef81e..cb7dc5c0 100644 --- a/src/graphql/api/public.ts +++ b/src/graphql/api/public.ts @@ -58,6 +58,7 @@ export const loadShouts = (options: LoadShoutsOptions) => { export const loadReactions = (options: QueryLoad_Reactions_ByArgs) => { const page = `${options.offset || 0}-${(options?.limit || 0) + (options.offset || 0)}` const filter = new URLSearchParams(options.by as Record) + console.debug(options) return cache(async () => { const resp = await defaultClient.query(loadReactionsByQuery, { ...options }).toPromise() const result = resp?.data?.load_reactions_by @@ -66,6 +67,7 @@ export const loadReactions = (options: QueryLoad_Reactions_ByArgs) => { } export const getShout = (options: QueryGet_ShoutArgs) => { + // console.debug('[lib.api] get shout cached fetcher returned', defaultClient) return cache( async () => { const resp = await defaultClient.query(loadReactionsByQuery, { ...options }).toPromise() diff --git a/src/graphql/query/core/authors-all.ts b/src/graphql/query/core/authors-all.ts index cfdc3d93..b039bf6e 100644 --- a/src/graphql/query/core/authors-all.ts +++ b/src/graphql/query/core/authors-all.ts @@ -1,7 +1,7 @@ import { gql } from '@urql/core' export default gql` - query { + query GetAuthorsAllQuery { get_authors_all { id slug diff --git a/src/graphql/query/core/authors-load-by.ts b/src/graphql/query/core/authors-load-by.ts index 2d60ddeb..0f24300d 100644 --- a/src/graphql/query/core/authors-load-by.ts +++ b/src/graphql/query/core/authors-load-by.ts @@ -1,8 +1,8 @@ import { gql } from '@urql/core' export default gql` - query AuthorsAllQuery($by: AuthorsBy!, $limit: Int, $offset: Int) { - get_authors(by: $by, limit: $limit, offset: $offset) { + query LoadAuthorsBy($by: AuthorsBy!, $limit: Int, $offset: Int) { + load_authors_by(by: $by, limit: $limit, offset: $offset) { id slug name diff --git a/src/routes/*404.tsx b/src/routes/*404.tsx index 3cf5f80c..b6cbf8cf 100644 --- a/src/routes/*404.tsx +++ b/src/routes/*404.tsx @@ -1,11 +1,12 @@ import { HttpStatusCode } from '@solidjs/start' +import { onMount } from 'solid-js' import { FourOuFourView } from '../components/Views/FourOuFour' import { PageLayout } from '../components/_shared/PageLayout' import { useLocalize } from '../context/localize' export default () => { const { t } = useLocalize() - + onMount(() => console.info('404 page')) return ( diff --git a/src/routes/[slug].tsx b/src/routes/[slug].tsx index 5bc4a774..353f5a27 100644 --- a/src/routes/[slug].tsx +++ b/src/routes/[slug].tsx @@ -1,8 +1,9 @@ -import { RouteSectionProps, createAsync, useLocation, useParams } from '@solidjs/router' -import { ErrorBoundary, Suspense, createEffect, createMemo, createSignal, on, onMount } from 'solid-js' -import { FourOuFourView } from '~/components/Views/FourOuFour' +import { RouteDefinition, RouteSectionProps, createAsync, redirect, useLocation, useParams } from '@solidjs/router' +import { HttpStatusCode } from '@solidjs/start' +import { ErrorBoundary, Show, createEffect, createMemo, createSignal, on, onMount } from 'solid-js' import { Loading } from '~/components/_shared/Loading' import { gaIdentity } from '~/config' +import { useFeed } from '~/context/feed' import { useLocalize } from '~/context/localize' import { getShout } from '~/graphql/api/public' import type { Shout } from '~/graphql/schema/core.gen' @@ -11,71 +12,83 @@ import { FullArticle } from '../components/Article/FullArticle' import { PageLayout } from '../components/_shared/PageLayout' import { ReactionsProvider } from '../context/reactions' -const fetchShout = async (slug: string) => { + +const fetchShout = async (slug: string): Promise => { const shoutLoader = getShout({ slug }) - return await shoutLoader() + const shout = await shoutLoader() + if (!shout) { + throw new Error('Shout not found') + } + return shout } -export const route = { - load: async ({ params }: RouteSectionProps<{ article: Shout }>) => await fetchShout(params.slug) + +export const route: RouteDefinition = { + load: async ({ params }) => { + try { + return await fetchShout(params.slug) + } catch (error) { + console.error('Error loading shout:', error) + throw new Response(null, { + status: 404, + statusText: 'Not Found' + }) + } + } } export default (props: RouteSectionProps<{ article: Shout }>) => { const params = useParams() const loc = useLocation() - const article = createAsync(async () => props.data.article || (await fetchShout(params.slug))) + const { articleEntities } = useFeed() const { t } = useLocalize() const [scrollToComments, setScrollToComments] = createSignal(false) - const title = createMemo( - () => `${article()?.authors?.[0]?.name || t('Discours')} :: ${article()?.title || ''}` - ) + + const article = createAsync(async () => { + if (params.slug && articleEntities?.()) { + return articleEntities()?.[params.slug] || props.data.article || await fetchShout(params.slug) + } + throw redirect('/404', { status: 404 }) + }) + + const title = createMemo(() => `${article()?.authors?.[0]?.name || t('Discours')} :: ${article()?.title || ''}`) + onMount(async () => { - if (gaIdentity) { + if (gaIdentity && article()?.id) { try { - console.info('[routes.slug] mounted, connecting ga...') await loadGAScript(gaIdentity) initGA(gaIdentity) - console.debug('[routes.slug] Google Analytics connected successfully') } catch (error) { - console.warn('[routes.slug] Failed to connect Google Analytics:', error) + console.warn('Failed to connect Google Analytics:', error) } } }) - createEffect( - on( - article, - (a?: Shout) => { - if (!a) return - console.debug('[routes.slug] article found') - window?.gtag?.('event', 'page_view', { - page_title: a.title, - page_location: window?.location.href || '', - page_path: loc.pathname - }) - }, - { defer: true } - ) - ) + createEffect(on(article, (a?: Shout) => { + if (!a) return + window?.gtag?.('event', 'page_view', { + page_title: a.title, + page_location: window?.location.href || '', + page_path: loc.pathname + }) + }, { defer: true })) return ( - }> - }> + }> + }> { - setScrollToComments(value) - }} + scrollToComments={(value) => setScrollToComments(value)} > - + ) } diff --git a/src/routes/author/(all-authors).tsx b/src/routes/author/(all-authors).tsx index 4cdb85e9..6f3a983d 100644 --- a/src/routes/author/(all-authors).tsx +++ b/src/routes/author/(all-authors).tsx @@ -1,5 +1,5 @@ import { RouteDefinition, RouteLoadFuncArgs, type RouteSectionProps, createAsync } from '@solidjs/router' -import { Suspense, createReaction } from 'solid-js' +import { Suspense, createEffect, on } from 'solid-js' import { AllAuthors } from '~/components/Views/AllAuthors' import { AUTHORS_PER_PAGE } from '~/components/Views/AllAuthors/AllAuthors' import { Loading } from '~/components/_shared/Loading' @@ -22,22 +22,56 @@ const fetchAllAuthors = async () => { } export const route = { - load: ({ location: { query } }: RouteLoadFuncArgs) => - fetchAuthorsWithStat(Number.parseInt(query.offset), query.by || 'name') + load: async ({ location: { query } }: RouteLoadFuncArgs) => { + const by = query.by + const isAll = !by || by === 'name' + return { + authors: isAll && await fetchAllAuthors(), + topFollowedAuthors: await fetchAuthorsWithStat(10, 'followers'), + topShoutsAuthors: await fetchAuthorsWithStat(10, 'shouts') + } as AllAuthorsData + } } satisfies RouteDefinition -export default function AllAuthorsPage(props: RouteSectionProps<{ authors: Author[] }>) { +type AllAuthorsData = { authors: Author[], topFollowedAuthors: Author[], topShoutsAuthors: Author[] } + +// addAuthors to context + +export default function AllAuthorsPage(props: RouteSectionProps) { const { t } = useLocalize() - const { authorsSorted, addAuthors } = useAuthors() - const authors = createAsync( - async () => authorsSorted?.() || props.data.authors || (await fetchAllAuthors()) - ) - createReaction(() => typeof addAuthors === 'function' && addAuthors?.(authors() || [])) + const { addAuthors } = useAuthors() + + // async load data: from ssr or fetch + const data = createAsync(async () => { + if (props.data) return props.data + return { + authors: await fetchAllAuthors(), + topFollowedAuthors: await fetchAuthorsWithStat(10, 'followers'), + topShoutsAuthors: await fetchAuthorsWithStat(10, 'shouts') + } as AllAuthorsData + }) + + // update context when data is loaded + createEffect(on([data, () => addAuthors], + ([data, aa])=> { + if(data && aa) { + aa(data.authors as Author[]) + aa(data.topFollowedAuthors as Author[]) + aa(data.topShoutsAuthors as Author[]) + console.debug('[routes.author] added all authors:', data.authors) + } + }, { defer: true} + )) + return ( }> - +