react-toolkits 0.0.3 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-toolkits",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "sideEffects": [
5
5
  "**/*.css"
6
6
  ],
@@ -1,6 +1,7 @@
1
1
  import type { FormInstance, FormProps } from 'antd'
2
2
  import { Button, Col, Form, Row, Space, theme } from 'antd'
3
3
  import type { PropsWithChildren } from 'react'
4
+ import * as React from 'react'
4
5
 
5
6
  export interface FilterFormProps<Values>
6
7
  extends Pick<
@@ -5,10 +5,9 @@ import type { Merge } from 'ts-essentials'
5
5
  import { createPortal } from 'react-dom'
6
6
 
7
7
  export type UseFormModalProps<T> = Merge<
8
- Omit<FormModalProps<T>, 'open' | 'onCancel' | 'closeFn' | 'children' | 'onConfirm'>,
8
+ Omit<FormModalProps<T>, 'open' | 'onCancel' | 'closeFn' | 'children'>,
9
9
  {
10
10
  content: FormModalProps<T>['children']
11
- onConfirm?: (values: T) => void
12
11
  }
13
12
  >
14
13
 
@@ -34,7 +33,7 @@ export function useFormModal<T extends object>(props: UseFormModalProps<T>) {
34
33
 
35
34
  const Modal = useMemo(() => {
36
35
  return (
37
- <FormModal {...restProps} open={open} closeFn={closeModal} title={title} onConfirm={onConfirm}>
36
+ <FormModal ref={formRef} {...restProps} open={open} closeFn={closeModal} title={title} onConfirm={onConfirm}>
38
37
  {content}
39
38
  </FormModal>
40
39
  )
@@ -1,7 +1,6 @@
1
- /* eslint-disable react/jsx-indent */
2
1
  import type { FormInstance, FormProps, ModalProps } from 'antd'
3
2
  import { Button, Form, Modal } from 'antd'
4
- import type { ForwardedRef } from 'react'
3
+ import type { ForwardedRef, ReactElement } from 'react'
5
4
  import { forwardRef, useId, useImperativeHandle, useRef, useState } from 'react'
6
5
 
7
6
  type RenderChildren<T> = (props: {
@@ -20,7 +19,7 @@ export type RecursivePartial<T> = T extends object
20
19
  ? RecursivePartial<T[P]>
21
20
  : T[P]
22
21
  }
23
- : any
22
+ : unknown
24
23
 
25
24
  export interface FormModalProps<T>
26
25
  extends Pick<ModalProps, 'width' | 'title' | 'open' | 'afterClose' | 'bodyStyle' | 'maskClosable'>,
@@ -29,7 +28,7 @@ export interface FormModalProps<T>
29
28
  footer?: ModalProps['footer']
30
29
  closeFn?: VoidFunction
31
30
  initialValues?: RecursivePartial<T>
32
- onConfirm?: (values: T) => void
31
+ onConfirm?: (values: T) => Promise<void>
33
32
  }
34
33
 
35
34
  export interface FormModalRef<T = object> {
@@ -81,17 +80,17 @@ const InternalFormModal = <T extends object>(props: FormModalProps<T>, ref: Forw
81
80
  typeof footer === 'object'
82
81
  ? footer
83
82
  : [
84
- <Button
83
+ <Button
85
84
  key="cancel"
86
85
  onClick={() => {
87
86
  closeFn?.()
88
87
  }}
89
- >
90
- 取消
91
- </Button>,
92
- <Button form={id} key="submit" type="primary" htmlType="submit" loading={confirmLoading}>
93
- 确定
94
- </Button>,
88
+ >
89
+ 取消
90
+ </Button>,
91
+ <Button form={id} key="submit" type="primary" htmlType="submit" loading={confirmLoading}>
92
+ 确定
93
+ </Button>,
95
94
  ]
96
95
  }
97
96
  afterClose={() => {
@@ -133,6 +132,6 @@ const InternalFormModal = <T extends object>(props: FormModalProps<T>, ref: Forw
133
132
 
134
133
  const FormModal = forwardRef(InternalFormModal) as <T extends object>(
135
134
  props: FormModalProps<T> & { ref?: ForwardedRef<FormModalRef<T>> },
136
- ) => React.ReactElement
135
+ ) => ReactElement
137
136
 
138
137
  export default FormModal
@@ -1,8 +1,8 @@
1
- import { useFetcher, usePermission } from '@/hooks'
1
+ import { useHttpClient, usePermission } from '@/hooks'
2
2
  import { useQueryTriggerStore } from '@/stores'
3
3
  import type { ListResponse, PaginationParams } from '@/types'
4
4
  import type { FormInstance, FormProps } from 'antd'
5
- import { Form, Result, Spin, Table } from 'antd'
5
+ import { Form, Result, Table } from 'antd'
6
6
  import type { TableProps } from 'antd/es/table'
7
7
  import type { AxiosRequestConfig } from 'axios'
8
8
  import type { ReactNode } from 'react'
@@ -27,7 +27,7 @@ export interface QueryListProps<Item, Values>
27
27
 
28
28
  const QueryList = <Item extends object, Values = NonNullable<unknown>>(props: QueryListProps<Item, Values>) => {
29
29
  const { code, confirmText, labelCol, swrKey, renderForm, transformArg, initialValues, ...tableProps } = props
30
- const { accessible, isValidating } = usePermission(code)
30
+ const { accessible } = usePermission(code ?? '')
31
31
  const [form] = Form.useForm<Values>()
32
32
  const setTrigger = useQueryTriggerStore(state => state.setTrigger)
33
33
 
@@ -36,7 +36,7 @@ const QueryList = <Item extends object, Values = NonNullable<unknown>>(props: Qu
36
36
  perPage: 10,
37
37
  })
38
38
 
39
- const fetcher = useFetcher()
39
+ const httpClient = useHttpClient()
40
40
 
41
41
  const { data, isMutating, trigger } = useSWRMutation(
42
42
  swrKey,
@@ -62,10 +62,10 @@ const QueryList = <Item extends object, Values = NonNullable<unknown>>(props: Qu
62
62
  ...newPaginationData,
63
63
  }
64
64
 
65
- return fetcher<ListResponse<Item>>({
65
+ return httpClient.request<ListResponse<Item>>({
66
66
  ...key,
67
- // TODO: 兼容 params data
68
- params: typeof transformArg === 'function' ? transformArg(fetcherArg) : fetcherArg,
67
+ [key.method === 'POST' ? 'data' : 'params']:
68
+ typeof transformArg === 'function' ? transformArg(fetcherArg) : fetcherArg,
69
69
  })
70
70
  },
71
71
  )
@@ -96,7 +96,7 @@ const QueryList = <Item extends object, Values = NonNullable<unknown>>(props: Qu
96
96
 
97
97
  useEffect(() => {
98
98
  setTrigger(swrKey, trigger)
99
- }, [swrKey, setTrigger, trigger])
99
+ }, [swrKey, trigger, setTrigger])
100
100
 
101
101
  useEffect(() => {
102
102
  ;(async () => {
@@ -109,19 +109,6 @@ const QueryList = <Item extends object, Values = NonNullable<unknown>>(props: Qu
109
109
  })()
110
110
  }, [form, trigger])
111
111
 
112
- if (isValidating) {
113
- return (
114
- <Spin
115
- style={{
116
- display: 'flex',
117
- justifyContent: 'center',
118
- alignItems: 'center',
119
- height: 200,
120
- }}
121
- />
122
- )
123
- }
124
-
125
112
  if (!accessible) {
126
113
  return <Result status={403} subTitle="无权限,请联系管理员进行授权" />
127
114
  }
@@ -1,3 +1 @@
1
1
  export const SSO_URL = 'https://idaas.ifunplus.cn/enduser/api/application/plugin_FunPlus/sso/v1'
2
-
3
- export const SECRET = new TextEncoder().encode('cc7e0d44fd473002f1c42167459001140ec6389b7353f8088f4d9a95f2f596f2')
@@ -1,4 +1,4 @@
1
- import { useFetcher, usePermission } from '@/hooks'
1
+ import { useHttpClient, usePermission } from '@/hooks'
2
2
  import useSWR from 'swr'
3
3
  import useSWRMutation from 'swr/mutation'
4
4
  import type { PermissionEnumItem, RoleEnumItem } from '../types'
@@ -29,7 +29,7 @@ export function useRole(name: string) {
29
29
  }
30
30
 
31
31
  export function useCreateRole() {
32
- const fetcher = useFetcher()
32
+ const httpClient = useHttpClient()
33
33
 
34
34
  return useSWRMutation(
35
35
  '/api/usystem/role/create',
@@ -40,12 +40,12 @@ export function useCreateRole() {
40
40
  }: {
41
41
  arg: { name: string; permissions: string[] }
42
42
  },
43
- ) => fetcher({ method: 'POST', url, data: arg }),
43
+ ) => httpClient.post(url, arg),
44
44
  )
45
45
  }
46
46
 
47
47
  export function useUpdateRole() {
48
- const fetcher = useFetcher()
48
+ const httpClient = useHttpClient()
49
49
 
50
50
  return useSWRMutation(
51
51
  '/api/usystem/role/update',
@@ -56,12 +56,12 @@ export function useUpdateRole() {
56
56
  }: {
57
57
  arg: { id: number; name: string; permissions: string[] }
58
58
  },
59
- ) => fetcher({ method: 'POST', url, data: arg }),
59
+ ) => httpClient.post(url, arg),
60
60
  )
61
61
  }
62
62
 
63
63
  export function useRemoveRole() {
64
- const fetcher = useFetcher()
64
+ const httpClient = useHttpClient()
65
65
 
66
66
  return useSWRMutation(
67
67
  '/api/usystem/role/delete',
@@ -72,12 +72,12 @@ export function useRemoveRole() {
72
72
  }: {
73
73
  arg: { id: number; name: string }
74
74
  },
75
- ) => fetcher({ method: 'POST', url, data: arg }),
75
+ ) => httpClient.post(url, arg),
76
76
  )
77
77
  }
78
78
 
79
79
  export function useCreateUser() {
80
- const fetcher = useFetcher()
80
+ const httpClient = useHttpClient()
81
81
 
82
82
  return useSWRMutation(
83
83
  '/api/usystem/user/create',
@@ -88,17 +88,12 @@ export function useCreateUser() {
88
88
  }: {
89
89
  arg: { name: string; roles: string[] }
90
90
  },
91
- ) =>
92
- fetcher({
93
- method: 'POST',
94
- url,
95
- data: arg,
96
- }),
91
+ ) => httpClient.post(url, arg),
97
92
  )
98
93
  }
99
94
 
100
95
  export function useUpdateUser() {
101
- const fetcher = useFetcher()
96
+ const httpClient = useHttpClient()
102
97
 
103
98
  return useSWRMutation(
104
99
  '/api/usystem/user/update',
@@ -109,17 +104,12 @@ export function useUpdateUser() {
109
104
  }: {
110
105
  arg: { id: string; name: string; roles: string[] }
111
106
  },
112
- ) =>
113
- fetcher({
114
- method: 'POST',
115
- url,
116
- data: arg,
117
- }),
107
+ ) => httpClient.post(url, arg),
118
108
  )
119
109
  }
120
110
 
121
111
  export function useRemoveUser() {
122
- const fetcher = useFetcher()
112
+ const httpClient = useHttpClient()
123
113
 
124
114
  return useSWRMutation(
125
115
  '/api/usystem/user/delete',
@@ -130,11 +120,6 @@ export function useRemoveUser() {
130
120
  }: {
131
121
  arg: { id: string; name: string }
132
122
  },
133
- ) =>
134
- fetcher({
135
- method: 'POST',
136
- url,
137
- data: arg,
138
- }),
123
+ ) => httpClient.post(url, arg),
139
124
  )
140
125
  }
@@ -1,2 +1,2 @@
1
- export * from './use-fetcher'
1
+ export * from './use-http-client'
2
2
  export * from './use-permission'
@@ -1,10 +1,7 @@
1
- import { useTokenStore } from '@/stores'
2
- import type { BackendResponse } from '@/types'
3
- import { App } from 'antd'
4
- import type { AxiosError, AxiosInstance, AxiosRequestConfig } from 'axios'
1
+ import {useTokenStore} from '@/stores'
2
+ import type {AxiosInstance, AxiosRequestConfig} from 'axios'
5
3
  import axios from 'axios'
6
- import { useNavigate } from 'react-router-dom'
7
- import type { Merge } from 'ts-essentials'
4
+ import type {Merge} from 'ts-essentials'
8
5
 
9
6
  // 覆盖 AxiosInstance 各种请求方法的返回值,为了方便我们在 interceptors.response 里把 AxiosResponse 直接打平成后端返回的数据,去掉了 axios 的封装。
10
7
  type ShimmedAxiosInstance = Merge<
@@ -20,22 +17,19 @@ type ShimmedAxiosInstance = Merge<
20
17
  }
21
18
  >
22
19
 
23
- export class FetcherError extends Error {
24
- code: number
20
+ export class HttpClientError extends Error {
21
+ code?: number
25
22
  // 跳过错误提示
26
23
  skip: boolean
27
24
 
28
- constructor(message: string, code: number, skip = false) {
25
+ constructor(message: string, code?: number, skip = false) {
29
26
  super(message)
30
27
  this.code = code
31
28
  this.skip = skip
32
29
  }
33
30
  }
34
31
 
35
- export function useFetcher() {
36
- const { notification } = App.useApp()
37
- const clearToken = useTokenStore(state => state.clearToken)
38
- const navigate = useNavigate()
32
+ export function useHttpClient() {
39
33
  const token = useTokenStore(state => state.token)
40
34
 
41
35
  const defaultOptions: AxiosRequestConfig = {
@@ -56,47 +50,27 @@ export function useFetcher() {
56
50
  return response.data.data
57
51
  }
58
52
 
59
- throw new FetcherError(response.data.msg, 0)
53
+ throw new HttpClientError(response.data.msg, 0)
60
54
  },
61
- (error: AxiosError<BackendResponse<unknown>>) => {
55
+ error => {
62
56
  if (error.response) {
63
57
  // 请求成功发出且服务器也响应了状态码,但状态码超出了 2xx 的范围
64
58
  if (error.response.status === 401) {
65
- throw new FetcherError('未登录或登录已过期', error.response.status)
59
+ throw new HttpClientError('未登录或登录已过期', error.response.status)
66
60
  } else if (error.response.status === 403) {
67
- throw new FetcherError('无权限,请联系管理员进行授权', error.response.status)
61
+ throw new HttpClientError('无权限,请联系管理员进行授权', error.response.status)
68
62
  } else if ([404, 405].includes(error.response.status)) {
69
- throw new FetcherError('Not Found or Method not Allowed', error.response.status, true)
63
+ throw new HttpClientError('Not Found or Method not Allowed', error.response.status, true)
70
64
  } else if (error.response.status === 412) {
71
- throw new FetcherError('未注册用户', error.response.status)
65
+ throw new HttpClientError('未注册用户', error.response.status)
72
66
  } else {
73
- throw new FetcherError(error.response.data?.msg, error.response.status)
67
+ throw new HttpClientError(error.response.data?.msg, error.response.status)
74
68
  }
75
- } else if (error.request) {
76
- // 请求已经成功发起,但没有收到响应
77
- console.log(error.request)
78
69
  }
79
70
 
80
- return Promise.reject(error)
71
+ throw new HttpClientError('无响应')
81
72
  },
82
73
  )
83
74
 
84
- return <T = unknown,>(config: AxiosRequestConfig) =>
85
- instance.request<T>(config).catch((err: FetcherError) => {
86
- switch (err.code) {
87
- case 401:
88
- case 412:
89
- clearToken()
90
- navigate(err.code === 401 ? '/login' : '/login?not_registered=1', { replace: true })
91
- break
92
- default:
93
- if (!err.skip) {
94
- notification.error({
95
- message: '请求出错',
96
- description: err.message,
97
- })
98
- }
99
- }
100
- throw err
101
- })
75
+ return instance
102
76
  }
@@ -1,12 +1,14 @@
1
1
  import useSWRImmutable from 'swr/immutable'
2
- import { useFetcher } from './use-fetcher'
2
+ import { useNavigate } from 'react-router-dom'
3
+ import { useHttpClient } from './use-http-client'
3
4
 
4
5
  export interface PermissionCheckResult {
5
6
  [k: string]: boolean
6
7
  }
7
8
 
8
9
  export function usePermissions(codes: string[]) {
9
- const fetcher = useFetcher()
10
+ const httpClient = useHttpClient()
11
+ const navigate = useNavigate()
10
12
 
11
13
  const { data, isLoading } = useSWRImmutable(
12
14
  codes.length > 0
@@ -17,7 +19,7 @@ export function usePermissions(codes: string[]) {
17
19
  }
18
20
  : null,
19
21
  config =>
20
- fetcher<PermissionCheckResult>(config).then(res => {
22
+ httpClient.request<PermissionCheckResult>(config).then(res => {
21
23
  if (res.has_all) {
22
24
  return codes.reduce(
23
25
  (acc, curr) => {
@@ -36,12 +38,19 @@ export function usePermissions(codes: string[]) {
36
38
  {} as Record<string, boolean>,
37
39
  )
38
40
  }),
41
+ {
42
+ suspense: true,
43
+ shouldRetryOnError: false,
44
+ onError() {
45
+ navigate('/login')
46
+ },
47
+ },
39
48
  )
40
49
 
41
50
  return { data, isLoading }
42
51
  }
43
52
 
44
- export function usePermission(code?: string) {
53
+ export function usePermission(code: string) {
45
54
  const { data, isLoading } = usePermissions(code ? [code] : [])
46
55
 
47
56
  if (!code) {
package/src/index.ts CHANGED
@@ -5,3 +5,4 @@ export * from './hooks'
5
5
  export * from './stores'
6
6
  export * from './pages'
7
7
  export * from './layouts'
8
+ export * from './constants'
@@ -1,11 +1,14 @@
1
1
  import logo from '@/assets/512_orange_nobackground.png'
2
- import { Layout as AntdLayout, theme } from 'antd'
2
+ import { Alert, Layout as AntdLayout, Spin, theme } from 'antd'
3
3
  import type { FC, PropsWithChildren, ReactNode } from 'react'
4
+ import { Suspense } from 'react'
4
5
  import { Link } from 'react-router-dom'
5
6
  import type { ItemType2 } from './NavBar'
6
7
  import NavBar from './NavBar'
8
+ import { usePermission } from '@/hooks'
7
9
 
8
10
  const { Header, Sider, Content } = AntdLayout
11
+ const { ErrorBoundary } = Alert
9
12
 
10
13
  export interface LayoutProps {
11
14
  title?: ReactNode
@@ -19,45 +22,80 @@ const Layout: FC<PropsWithChildren<LayoutProps>> = props => {
19
22
  token: { colorBgContainer, colorBorder },
20
23
  } = theme.useToken()
21
24
 
25
+ // 为了验证 token 是否过期的请求,此处无其他用处。
26
+ usePermission('100001')
27
+
22
28
  return (
23
29
  <AntdLayout hasSider className="h-screen">
24
- <Sider
25
- width={256}
26
- style={{
27
- overflow: 'auto',
28
- height: '100vh',
29
- position: 'fixed',
30
- left: 0,
31
- top: 0,
32
- bottom: 0,
33
- borderRightWidth: 1,
34
- borderRightStyle: 'solid',
35
- borderRightColor: colorBorder,
36
- }}
37
- theme="light"
38
- >
39
- <div className="flex items-end px-6 py-4">
40
- <img src={logo} alt="logo" className="w-8 h-8" />
41
- <Link className="font-bold text-lg ml-2" to="/">
42
- {title}
43
- </Link>
44
- </div>
45
- <NavBar items={items} />
46
- </Sider>
47
- <AntdLayout className="ml-64">
48
- <Header
49
- style={{
50
- padding: '0 24px',
51
- background: colorBgContainer,
52
- borderBottomWidth: 1,
53
- borderBottomStyle: 'solid',
54
- borderBottomColor: colorBorder,
55
- }}
30
+ <ErrorBoundary>
31
+ <Suspense
32
+ fallback={
33
+ <Spin
34
+ style={{
35
+ display: 'flex',
36
+ justifyContent: 'center',
37
+ alignItems: 'center',
38
+ width: '100vw',
39
+ height: '100vh',
40
+ }}
41
+ />
42
+ }
56
43
  >
57
- {header}
58
- </Header>
59
- <Content className="p-6 overflow-auto bg-gray-50">{children}</Content>
60
- </AntdLayout>
44
+ <Sider
45
+ width={256}
46
+ style={{
47
+ overflow: 'auto',
48
+ height: '100vh',
49
+ position: 'fixed',
50
+ left: 0,
51
+ top: 0,
52
+ bottom: 0,
53
+ borderRightWidth: 1,
54
+ borderRightStyle: 'solid',
55
+ borderRightColor: colorBorder,
56
+ }}
57
+ theme="light"
58
+ >
59
+ <div className="flex items-end px-6 py-4">
60
+ <img src={logo} alt="logo" className="w-8 h-8" />
61
+ <Link className="font-bold text-lg ml-2" to="/">
62
+ {title}
63
+ </Link>
64
+ </div>
65
+
66
+ <NavBar items={items} />
67
+ </Sider>
68
+ <AntdLayout className="ml-64">
69
+ <Header
70
+ style={{
71
+ padding: '0 24px',
72
+ background: colorBgContainer,
73
+ borderBottomWidth: 1,
74
+ borderBottomStyle: 'solid',
75
+ borderBottomColor: colorBorder,
76
+ }}
77
+ >
78
+ {header}
79
+ </Header>
80
+ <Content className="p-6 overflow-auto bg-gray-50">
81
+ <Suspense
82
+ fallback={
83
+ <Spin
84
+ style={{
85
+ display: 'flex',
86
+ justifyContent: 'center',
87
+ alignItems: 'center',
88
+ height: '50vh',
89
+ }}
90
+ />
91
+ }
92
+ >
93
+ {children}
94
+ </Suspense>
95
+ </Content>
96
+ </AntdLayout>
97
+ </Suspense>
98
+ </ErrorBoundary>
61
99
  </AntdLayout>
62
100
  )
63
101
  }
@@ -1,6 +1,6 @@
1
- import { usePermissions } from '@/hooks'
2
- import { useMenuStore } from '@/stores'
3
- import { Menu, Spin } from 'antd'
1
+ import {usePermissions} from '@/hooks'
2
+ import {useMenuStore} from '@/stores'
3
+ import {Menu} from 'antd'
4
4
  import type {
5
5
  ItemType,
6
6
  MenuDividerType,
@@ -8,10 +8,10 @@ import type {
8
8
  MenuItemType,
9
9
  SubMenuType,
10
10
  } from 'antd/es/menu/hooks/useItems'
11
- import type { FC, ReactNode } from 'react'
12
- import { useCallback, useEffect, useMemo } from 'react'
13
- import { Link, useLocation } from 'react-router-dom'
14
- import type { Merge } from 'ts-essentials'
11
+ import type {FC, ReactNode} from 'react'
12
+ import {useCallback, useEffect, useMemo} from 'react'
13
+ import {Link, useLocation} from 'react-router-dom'
14
+ import type {Merge} from 'ts-essentials'
15
15
 
16
16
  // 扩展 antd Menu 的类型,使其支持一些我们想要的自定义字段。
17
17
  type MenuItemType2 = Merge<
@@ -107,7 +107,7 @@ const NavBar: FC<NavBarProps> = props => {
107
107
  const location = useLocation()
108
108
  const flattenItems = useMemo(() => flatItems(items ?? []), [items])
109
109
  const codes = flattenItems.map(item => item.code).filter(Boolean) as string[]
110
- const { data: permissions, isLoading } = usePermissions(codes)
110
+ const { data: permissions } = usePermissions(codes)
111
111
  const internalItems = useMemo(() => transformItems(items ?? [], permissions), [items, permissions])
112
112
 
113
113
  const openKeys = useMenuStore(state => state.openKeys)
@@ -136,19 +136,6 @@ const NavBar: FC<NavBarProps> = props => {
136
136
  }
137
137
  }, [flattenItems, location, setOpenKeys, setSelectedKeys])
138
138
 
139
- if (isLoading) {
140
- return (
141
- <Spin
142
- style={{
143
- display: 'flex',
144
- justifyContent: 'center',
145
- alignItems: 'center',
146
- height: 'calc(100vh - 64px)',
147
- }}
148
- />
149
- )
150
- }
151
-
152
139
  return (
153
140
  <Menu
154
141
  style={{ borderRight: 'none' }}
@@ -7,6 +7,7 @@ import { useEffect, useState } from 'react'
7
7
  import { Navigate, useSearchParams } from 'react-router-dom'
8
8
  import useSWRImmutable from 'swr/immutable'
9
9
  import Default from './default'
10
+ import { useHttpClient } from '@/hooks'
10
11
 
11
12
  const { Title } = Typography
12
13
 
@@ -16,6 +17,7 @@ const Login: FC<PropsWithChildren> = props => {
16
17
  const token = useTokenStore(state => state.token)
17
18
  const setToken = useTokenStore(state => state.setToken)
18
19
  const [showAlert, setShowAlert] = useState(false)
20
+ const httpClient = useHttpClient()
19
21
 
20
22
  const { isLoading } = useSWRImmutable<{ token: string }>(
21
23
  searchParams.has('ticket')
@@ -26,6 +28,7 @@ const Login: FC<PropsWithChildren> = props => {
26
28
  },
27
29
  }
28
30
  : null,
31
+ config => httpClient.request(config),
29
32
  {
30
33
  onSuccess: data => {
31
34
  setToken(data.token)
@@ -0,0 +1,27 @@
1
+ import { Button, Result } from 'antd'
2
+ import { useNavigate } from 'react-router-dom'
3
+
4
+ const NoMatch = () => {
5
+ const navigate = useNavigate()
6
+ return (
7
+ <div className="h-screen flex justify-center items-center">
8
+ <Result
9
+ status="404"
10
+ title="404"
11
+ subTitle="Sorry, the page you visited does not exist."
12
+ extra={
13
+ <Button
14
+ type="primary"
15
+ onClick={() => {
16
+ navigate('/')
17
+ }}
18
+ >
19
+ Back Home
20
+ </Button>
21
+ }
22
+ />
23
+ </div>
24
+ )
25
+ }
26
+
27
+ export default NoMatch