diff --git a/packages/datasource/src/index.js b/packages/datasource/src/index.js deleted file mode 100644 index c33a65188..000000000 --- a/packages/datasource/src/index.js +++ /dev/null @@ -1,31 +0,0 @@ -import * as form from './form'; -import { defaultEnvDomains } from './config.domains'; -import { getData as getNavData } from './nav'; - -export * as api from './api'; -export { useApiErrorListener } from './api/events'; - -export const getFormData = () => form; - -export const getData = ({ domain, path, locale, env, envDomainConfig, meData, redDots = {} } = {}) => { - const defaultEnv = (typeof process !== 'undefined' && process?.env?.NEXT_PUBLIC_RUNTIME_ENV) || 'production'; - - env = env || defaultEnv; - if (!['production', 'local'].includes(env)) { - env = 'preview'; - } - - const domainConfig = (envDomainConfig || defaultEnvDomains)[env]; - - return { - nav: getNavData({ - domain, - domainConfig, - env, - locale, - path, - meData, - redDots, - }), - }; -}; diff --git a/packages/datasource/src/index.ts b/packages/datasource/src/index.ts new file mode 100644 index 000000000..35f91231d --- /dev/null +++ b/packages/datasource/src/index.ts @@ -0,0 +1,6 @@ +import * as form from './form'; + +export * as api from './api'; +export { useApiErrorListener } from './api/events'; + +export const getFormData = () => form; diff --git a/src/components/errorPage/ErrorPage.component.js b/src/components/errorPage/ErrorPage.component.js index f5a073782..e46834abd 100644 --- a/src/components/errorPage/ErrorPage.component.js +++ b/src/components/errorPage/ErrorPage.component.js @@ -2,43 +2,25 @@ // https://nextjs.org/docs/advanced-features/custom-error-page // https://github.com/vercel/next.js/blob/canary/packages/next/pages/_error.tsx -import * as R from 'ramda'; -import React, { useEffect } from 'react'; +import React, {useEffect, useState} from 'react'; import { ArrowLeftOutlined } from '@ant-design/icons'; import { Button } from 'antd'; import { useRouter } from 'next/router'; import * as Styled from './errorPage.styled'; -import Svg403 from './403.svg'; -import Svg404 from './404.svg'; -import Svg500 from './500.svg'; import { CommunityHead } from '~/components'; import { CoreLayout } from '~/layouts'; - -const icons = { - 403: Svg403, - 404: Svg404, - 500: Svg500, -}; - -const errorMsgs = { - 403: '抱歉,您没有权限访问该页面', - 404: '您访问的页面不存在', - 500: '服务器异常,请稍后重试', -}; +import ErrorStatus from "~/components/errorPage/ErrorStatus"; const ErrorPage = ({ statusCode, errorMsg, error = undefined }) => { const router = useRouter(); - const Icon = R.propOr(icons[500], statusCode)(icons); - + const [returnToHomepageLoading, setReturnToHomepageLoading] = useState(false) useEffect(() => { if (error) { console.error(error); } }, [error]); - errorMsg = errorMsg || R.propOr('未知错误,请稍后重试', statusCode)(errorMsgs); - const headProps = { description: '', keyword: '', @@ -48,8 +30,11 @@ const ErrorPage = ({ statusCode, errorMsg, error = undefined }) => { const buttonProps = { type: 'primary', icon: , - onClick: () => { - router.push('/community', '/'); + loading: returnToHomepageLoading, + onClick: async () => { + setReturnToHomepageLoading(true) + await router.push('/community', '/') + setReturnToHomepageLoading(false) }, }; @@ -58,10 +43,7 @@ const ErrorPage = ({ statusCode, errorMsg, error = undefined }) => { - - - - {errorMsg} + diff --git a/src/components/errorPage/ErrorStatus.styled.ts b/src/components/errorPage/ErrorStatus.styled.ts new file mode 100644 index 000000000..b4dc9c23e --- /dev/null +++ b/src/components/errorPage/ErrorStatus.styled.ts @@ -0,0 +1,30 @@ +import styled from 'styled-components'; +import { mixins } from '@tidb-community/ui'; + +export const Container = styled.div` + display: flex; + justify-content: center; + align-items: center; + padding: 16px; +`; + +export const Main = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + padding: 16px; +`; + +export const IconWrapper = styled.div` + svg { + width: 30vw; + max-width: 280px; + min-width: 160px; + } +`; + +export const Message = styled.div` + ${mixins.typography('p1')}; + margin: 1.5rem 0 1rem; +`; diff --git a/src/components/errorPage/ErrorStatus.tsx b/src/components/errorPage/ErrorStatus.tsx new file mode 100644 index 000000000..a1b471b17 --- /dev/null +++ b/src/components/errorPage/ErrorStatus.tsx @@ -0,0 +1,44 @@ +import * as React from "react" + +import * as Styled from './ErrorStatus.styled' +import Svg403 from './403.svg'; +import Svg404 from './404.svg'; +import Svg500 from './500.svg'; + +export interface IProps extends React.HTMLAttributes { + statusCode?: number + errorMsg?: React.ReactNode +} + +const icons = { + 403: Svg403, + 404: Svg404, + 500: Svg500, +}; + +const errorMessages = { + 403: '抱歉,您没有权限访问该页面', + 404: '您访问的页面不存在', + 500: '服务器异常,请稍后重试', +}; + +const ErrorStatus: React.FC = (props) => { + const {statusCode, errorMsg, ...rest} = props + + const statusCodeOutput = props.statusCode ?? 500 + const errorMsgOutput = props.errorMsg ?? errorMessages[statusCodeOutput] + const Icon = icons[statusCodeOutput] + + return ( + + + + + + {errorMsgOutput} + + + ) +} + +export default ErrorStatus diff --git a/src/components/errorPage/errorPage.styled.js b/src/components/errorPage/errorPage.styled.js index 6881ead43..c11b95cc8 100644 --- a/src/components/errorPage/errorPage.styled.js +++ b/src/components/errorPage/errorPage.styled.js @@ -8,16 +8,3 @@ export const Container = styled.div` flex-direction: column; flex: 1; `; - -export const IconWrapper = styled.div` - svg { - width: 30vw; - max-width: 280px; - min-width: 160px; - } -`; - -export const Message = styled.div` - ${mixins.typography('p1')}; - margin: 1.5rem 0 1rem; -`; diff --git a/src/hooks/account.ts b/src/hooks/account.ts new file mode 100644 index 000000000..0a4647ac1 --- /dev/null +++ b/src/hooks/account.ts @@ -0,0 +1,63 @@ +import useSWR from "swr"; +import axios, {AxiosResponse} from "axios"; +import {useEffect} from "react"; + +export type TMe = { + "detail": string // "成功", + "data": { + "id": number // 3699, + "openid": string // "", + "username": string // "cw1997", + "avatar_url": string // "https://asktug.com/letter_avatar_proxy/v4/letter/c/e5b9ba/50.png", + "is_staff": boolean // false + } +} + +const accountsBaseUrl = process.env.NEXT_PUBLIC_ACCOUNTS_BASE_URL; +//const homeUrl = process.env.NEXT_PUBLIC_HOME_URL; +const loginUrl = `${accountsBaseUrl}/login`; +//const logoutUrl = `${accountsBaseUrl}/logout`; + +export const login = (redirectUrl?: string) => { + const { location } = window; + location.href = `${loginUrl}?redirect_to=${encodeURIComponent(redirectUrl ?? window.location.href)}`; +} + +/*export const logout = (redirectUrl?: string) => { + const { location } = window; + redirectUrl = redirectUrl ?? this.isAuthRequired ? homeUrl : location.href; + location.href = `${logoutUrl}?redirect_to=${encodeURIComponent(redirectUrl)}`; +};*/ + +const fetcher = async (): Promise> => { + const axiosInstance = axios.create({ + baseURL: process.env.NEXT_PUBLIC_API_BASE_URL ?? '', + withCredentials: true, + }) + const result = await axiosInstance.get('/api/me') + return result +} + +export const useAccount = (requireAuth = true) => { + const swrResponse = useSWR(['account'], fetcher, { + revalidateOnReconnect: true, + revalidateOnFocus: true, + }) + useEffect(() => { + if (requireAuth && !swrResponse.isValidating) { + if (swrResponse.data?.status !== 200 || swrResponse.error) { + login() + } + } + }, [requireAuth, swrResponse.isValidating, swrResponse.data?.status, swrResponse.error]) + return swrResponse +} + +export const useIsLoggedIn = (): null|boolean => { + const swrResponse = useSWR(['account'], fetcher, { + revalidateOnReconnect: true, + revalidateOnFocus: true, + }) + if (swrResponse.isValidating) return null + return swrResponse.data?.status === 200 +} diff --git a/src/layouts/CoreLayout/CoreLayout.tsx b/src/layouts/CoreLayout/CoreLayout.tsx new file mode 100644 index 000000000..564885dc3 --- /dev/null +++ b/src/layouts/CoreLayout/CoreLayout.tsx @@ -0,0 +1,74 @@ +import React from 'react'; +import type { FC } from 'react'; +import Image from 'next/image' +import { Footer, Header } from '@pingcap-inc/tidb-community-site-components'; + +import * as Styled from './site.styled'; +import beianIconImage from './beian.png' +import {ActivityBanner} from "../../../packages/ui"; +import * as bannerData from "~/data/banner"; + +export interface IProps { + MainWrapper?: FC; + backgroundColor?: string; +} + +const CoreLayout: FC = ({ MainWrapper = Styled.Main, backgroundColor, children }) => { + const currentYear = new Date().getFullYear(); + return ( + + +
+ {children} +