diff --git a/src/components/biz/WrapperNextPage/index.tsx b/src/components/biz/WrapperNextPage/index.tsx new file mode 100644 index 000000000..ab3c324f0 --- /dev/null +++ b/src/components/biz/WrapperNextPage/index.tsx @@ -0,0 +1,96 @@ +import type { AxiosError } from 'axios' +import { isNumber } from 'lodash-es' +import type { NextPage, NextPageContext } from 'next' +import type { NextRouter } from 'next/router' +import { useRouter } from 'next/router' +import type { FC } from 'react' +import { useEffect, useState } from 'react' + +import { RequestError } from '@mx-space/api-client' + +import { Loading } from '~/components/universal/Loading' +import { isClientSide } from '~/utils/env' + +import { ErrorView } from '../Error' + +const createMockContext = (router: NextRouter): NextPageContext => { + return { + AppTree: () => null, + pathname: router.pathname, + query: router.query, + asPath: router.asPath, + } +} + +export function wrapperNextPage>(NextPage: T) { + if (isClientSide()) { + const Page: FC = () => { + const router = useRouter() + const [loading, setLoading] = useState( + NextPage.getInitialProps ? true : false, + ) + + const [error, setError] = useState(null) + + const [dataProps, setProps] = useState(null) + + useEffect(() => { + if (!NextPage.getInitialProps) { + return + } + + try { + const task = NextPage.getInitialProps(createMockContext(router)) + const isPromise = task.then + if (isPromise) { + task + .then((data) => { + setLoading(false) + setProps(data) + }) + .catch((err) => { + setLoading(false) + setError(err) + }) + } else { + setLoading(false) + setProps(task) + } + } catch (err: any) { + setLoading(false) + setError(err) + } + }, []) + + if (error) { + let code: any + if (error instanceof RequestError) { + // @see: https://github.com/axios/axios/pull/3645 + const axiosError = error.raw as AxiosError + + code = isNumber(axiosError.response?.status) + ? axiosError.response!.status + : 408 + } + + return ( + + ) + } + + if (!dataProps && loading) { + return + } + + // @ts-ignore + return + } + return Page as T + } + + return NextPage +} diff --git a/src/pages/categories/[slug].tsx b/src/pages/categories/[slug].tsx index b5e4657df..9ad207fc3 100644 --- a/src/pages/categories/[slug].tsx +++ b/src/pages/categories/[slug].tsx @@ -8,6 +8,7 @@ import type { CategoryWithChildrenModel, } from '@mx-space/api-client' +import { wrapperNextPage } from '~/components/biz/WrapperNextPage' import { TimelineListWrapper } from '~/components/universal/TimelineListWrapper' import { BottomUpTransitionView } from '~/components/universal/Transition/bottom-up' import { apiClient } from '~/utils/client' @@ -77,4 +78,4 @@ CategoryListView.getInitialProps = async (ctx) => { } } -export default CategoryListView +export default wrapperNextPage(CategoryListView) diff --git a/src/pages/friends/index.tsx b/src/pages/friends/index.tsx index 85a5416d2..f8c592884 100644 --- a/src/pages/friends/index.tsx +++ b/src/pages/friends/index.tsx @@ -6,6 +6,7 @@ import { createElement, useEffect, useState } from 'react' import type { LinkModel } from '@mx-space/api-client' import { LinkState, LinkType } from '@mx-space/api-client' +import { wrapperNextPage } from '~/components/biz/WrapperNextPage' import { BannedSection, FavoriteSection, @@ -158,4 +159,4 @@ FriendsView.getInitialProps = async () => { return { friends: shuffle(friends), collections, outdated, banned } } -export default FriendsView +export default wrapperNextPage(FriendsView) diff --git a/src/pages/notes/[id].tsx b/src/pages/notes/[id].tsx index 2470f9154..a64f0e3ef 100644 --- a/src/pages/notes/[id].tsx +++ b/src/pages/notes/[id].tsx @@ -18,6 +18,7 @@ import { useUpdate } from 'react-use' import type { NoteModel } from '@mx-space/api-client' import { RequestError } from '@mx-space/api-client' +import { wrapperNextPage } from '~/components/biz/WrapperNextPage' import { NoteFooterActionBar } from '~/components/in-page/Note/NoteActionBar' import { NoteFooterActionBarForMobile } from '~/components/in-page/Note/NoteFooterNavigation' import { NoteMarkdownRender } from '~/components/in-page/Note/NoteMarkdownRender' @@ -321,4 +322,4 @@ PP.getInitialProps = async (ctx) => { } } -export default PP +export default wrapperNextPage(PP) diff --git a/src/pages/notes/topics/[topicSlug].tsx b/src/pages/notes/topics/[topicSlug].tsx index a61ffc176..e1c1b604e 100644 --- a/src/pages/notes/topics/[topicSlug].tsx +++ b/src/pages/notes/topics/[topicSlug].tsx @@ -8,6 +8,7 @@ import type { NoteModel, Pager } from '@mx-space/api-client' import type { TopicModel } from '@mx-space/api-client/types/models/topic' import { SEO } from '~/components/biz/Seo' +import { wrapperNextPage } from '~/components/biz/WrapperNextPage' import { ArticleLayout } from '~/components/layouts/ArticleLayout' import { Divider } from '~/components/universal/Divider' import { Pagination } from '~/components/universal/Pagination' @@ -93,4 +94,4 @@ TopicDetailPage.getInitialProps = async (ctx) => { const { topicSlug } = ctx.query return await apiClient.topic.getTopicBySlug(topicSlug as string) } -export default TopicDetailPage +export default wrapperNextPage(TopicDetailPage) diff --git a/src/pages/posts/[category]/[slug].tsx b/src/pages/posts/[category]/[slug].tsx index 39048c428..b779a3da7 100644 --- a/src/pages/posts/[category]/[slug].tsx +++ b/src/pages/posts/[category]/[slug].tsx @@ -12,6 +12,7 @@ import type { PostModel } from '@mx-space/api-client' import { buildStoreDataLoadableView } from '~/components/biz/LoadableView' import { Seo } from '~/components/biz/Seo' +import { wrapperNextPage } from '~/components/biz/WrapperNextPage' import { PostRelated } from '~/components/in-page/Post/post-related' import { ArticleLayout } from '~/components/layouts/ArticleLayout' import { @@ -295,4 +296,4 @@ PP.getInitialProps = async (ctx) => { return data } -export default PP +export default wrapperNextPage(PP) diff --git a/src/pages/projects/[id].tsx b/src/pages/projects/[id].tsx index 999f46d2e..d52d5b6d1 100644 --- a/src/pages/projects/[id].tsx +++ b/src/pages/projects/[id].tsx @@ -2,6 +2,7 @@ import type { NextPage } from 'next' import type { ProjectModel } from '@mx-space/api-client' +import { wrapperNextPage } from '~/components/biz/WrapperNextPage' import { ProjectDetail } from '~/components/in-page/Project/detail' import { useStore } from '~/store' import { apiClient } from '~/utils/client' @@ -35,4 +36,4 @@ ProjectView.getInitialProps = async (ctx) => { return data } -export default ProjectView +export default wrapperNextPage(ProjectView) diff --git a/src/pages/timeline/index.tsx b/src/pages/timeline/index.tsx index 38da9c98e..8ae9d80f3 100644 --- a/src/pages/timeline/index.tsx +++ b/src/pages/timeline/index.tsx @@ -8,6 +8,7 @@ import { usePrevious } from 'react-use' import type { TimelineData } from '@mx-space/api-client' +import { wrapperNextPage } from '~/components/biz/WrapperNextPage' import { SolidBookmark } from '~/components/universal/Icons' import { NumberTransition } from '~/components/universal/NumberRecorder' import { TimelineListWrapper } from '~/components/universal/TimelineListWrapper' @@ -301,4 +302,4 @@ TimeLineView.getInitialProps = async (ctx) => { memory: !!memory, } as TimeLineViewProps } -export default TimeLineView +export default wrapperNextPage(TimeLineView)