diff --git a/packages/console/package.json b/packages/console/package.json index 83b993b1a..32a1cb87f 100644 --- a/packages/console/package.json +++ b/packages/console/package.json @@ -25,7 +25,8 @@ "react": "^17.0.2", "react-dom": "^17.0.2", "react-i18next": "^11.15.4", - "react-router-dom": "^6.2.2" + "react-router-dom": "^6.2.2", + "swr": "^1.2.2" }, "devDependencies": { "@parcel/core": "^2.3.1", diff --git a/packages/console/src/App.tsx b/packages/console/src/App.tsx index c6a78a1b0..549848720 100644 --- a/packages/console/src/App.tsx +++ b/packages/console/src/App.tsx @@ -1,7 +1,8 @@ import React, { useEffect } from 'react'; import { BrowserRouter, Route, Routes, useLocation, useNavigate } from 'react-router-dom'; - +import { SWRConfig } from 'swr'; import './scss/normalized.scss'; + import * as styles from './App.module.scss'; import AppContent from './components/AppContent'; import Content from './components/Content'; @@ -10,6 +11,7 @@ import Topbar from './components/Topbar'; import initI18n from './i18n/init'; import ApiResources from './pages/ApiResources'; import Applications from './pages/Applications'; +import { fetcher } from './swr'; const isBasenameNeeded = process.env.NODE_ENV !== 'development' || process.env.PORT === '5002'; @@ -26,18 +28,20 @@ const Main = () => { }, [location.pathname, navigate]); return ( - - -
- - - - } /> - } /> - - -
-
+ + + +
+ + + + } /> + } /> + + +
+
+
); }; diff --git a/packages/console/src/components/ItemPreview/index.tsx b/packages/console/src/components/ItemPreview/index.tsx index e45186867..096ec9b72 100644 --- a/packages/console/src/components/ItemPreview/index.tsx +++ b/packages/console/src/components/ItemPreview/index.tsx @@ -14,7 +14,7 @@ const ItemPreview = ({ title, subtitle, icon }: Props) => { {icon}
{title}
- {subtitle &&
{subtitle}
} + {subtitle &&
{String(subtitle)}
}
); diff --git a/packages/console/src/pages/Applications/index.module.scss b/packages/console/src/pages/Applications/index.module.scss index a4e35cb5f..ec4c746f9 100644 --- a/packages/console/src/pages/Applications/index.module.scss +++ b/packages/console/src/pages/Applications/index.module.scss @@ -9,7 +9,7 @@ margin-top: _.unit(6); width: 100%; - tbody tr { + tbody tr.clickable { cursor: pointer; &:hover { diff --git a/packages/console/src/pages/Applications/index.tsx b/packages/console/src/pages/Applications/index.tsx index 2486694e3..671305431 100644 --- a/packages/console/src/pages/Applications/index.tsx +++ b/packages/console/src/pages/Applications/index.tsx @@ -1,4 +1,7 @@ +import { Application } from '@logto/schemas'; import React from 'react'; +import { useTranslation } from 'react-i18next'; +import useSWR from 'swr'; import Button from '@/components/Button'; import Card from '@/components/Card'; @@ -6,10 +9,16 @@ import CardTitle from '@/components/CardTitle'; import CopyToClipboard from '@/components/CopyToClipboard'; import ImagePlaceholder from '@/components/ImagePlaceholder'; import ItemPreview from '@/components/ItemPreview'; +import { RequestError } from '@/swr'; +import { applicationTypeI18nKey } from '@/types/applications'; import * as styles from './index.module.scss'; const Applications = () => { + const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); + const { data, error } = useSWR('/api/applications'); + const isLoading = !data && !error; + return (
@@ -19,23 +28,35 @@ const Applications = () => { - - + + - - - - + {error && ( + + + + )} + {isLoading && ( + + + + )} + {data?.map(({ id, name, type }) => ( + + + + + ))}
Application NameClient ID{t('applications.application_name')}{t('applications.client_id')}
- } - /> - - -
error occurred: {error.metadata.code}
loading
+ } + /> + + +
diff --git a/packages/console/src/swr/index.ts b/packages/console/src/swr/index.ts new file mode 100644 index 000000000..4cba5752d --- /dev/null +++ b/packages/console/src/swr/index.ts @@ -0,0 +1,22 @@ +import { RequestErrorMetadata } from '@logto/schemas'; +import { BareFetcher } from 'swr'; + +export class RequestError extends Error { + metadata: RequestErrorMetadata; + + constructor(metadata: RequestErrorMetadata) { + super('Request error occurred.'); + this.metadata = metadata; + } +} + +export const fetcher: BareFetcher = async (resource, init) => { + const response = await fetch(resource, init); + + if (!response.ok) { + const metadata = (await response.json()) as RequestErrorMetadata; + throw new RequestError(metadata); + } + + return response.json(); +}; diff --git a/packages/console/src/types/applications.ts b/packages/console/src/types/applications.ts new file mode 100644 index 000000000..2542799eb --- /dev/null +++ b/packages/console/src/types/applications.ts @@ -0,0 +1,8 @@ +import { AdminConsoleKey } from '@logto/phrases'; +import { ApplicationType } from '@logto/schemas'; + +export const applicationTypeI18nKey: Record = { + [ApplicationType.Native]: 'applications.type.native', + [ApplicationType.SPA]: 'applications.type.spa', + [ApplicationType.Traditional]: 'applications.type.tranditional', +}; diff --git a/packages/core/src/include.d/koa.d.ts b/packages/core/src/include.d/koa.d.ts index 354088a9f..522e390cd 100644 --- a/packages/core/src/include.d/koa.d.ts +++ b/packages/core/src/include.d/koa.d.ts @@ -1,4 +1,4 @@ -import { DefaultState, DefaultContext, ParameterizedContext, Next } from 'koa'; +import { DefaultState, DefaultContext, ParameterizedContext, Next, BaseRequest } from 'koa'; declare module 'koa' { // Have to do this patch since `compose.Middleware` returns `any`. @@ -10,4 +10,9 @@ declare module 'koa' { ResponseBodyT = any, NextT = void > = KoaMiddleware, NextT>; + + interface Request extends BaseRequest { + body?: any; + files?: Files; + } } diff --git a/packages/phrases/src/locales/en.ts b/packages/phrases/src/locales/en.ts index ab87b452d..b7f3a3835 100644 --- a/packages/phrases/src/locales/en.ts +++ b/packages/phrases/src/locales/en.ts @@ -38,6 +38,13 @@ const translation = { subtitle: 'Setup a mobile, single page or traditional application to use Logto for authentication.', create: 'Create Application', + application_name: 'Application Name', + client_id: 'Client ID', + type: { + native: 'Native App', + spa: 'Single Page App', + tranditional: 'Tranditional Web App', + }, }, }, }; diff --git a/packages/phrases/src/locales/zh-cn.ts b/packages/phrases/src/locales/zh-cn.ts index 105d483f7..c483105ff 100644 --- a/packages/phrases/src/locales/zh-cn.ts +++ b/packages/phrases/src/locales/zh-cn.ts @@ -40,6 +40,13 @@ const translation = { subtitle: 'Setup a mobile, single page or traditional application to use Logto for authentication.', create: 'Create Application', + application_name: 'Application Name', + client_id: 'Client ID', + type: { + native: 'Native App', + spa: 'Single Page App', + tranditional: 'Tranditional Web App', + }, }, }, }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9839b537e..17a9996cd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -46,6 +46,7 @@ importers: react-i18next: ^11.15.4 react-router-dom: ^6.2.2 stylelint: ^13.13.1 + swr: ^1.2.2 typescript: ^4.5.5 dependencies: '@logto/phrases': link:../phrases @@ -58,6 +59,7 @@ importers: react-dom: 17.0.2_react@17.0.2 react-i18next: 11.15.4_2c37a602a29bb6bd53f3de707a8cfcc5 react-router-dom: 6.2.2_react-dom@17.0.2+react@17.0.2 + swr: 1.2.2_react@17.0.2 devDependencies: '@parcel/core': 2.3.1 '@parcel/transformer-sass': 2.3.1_@parcel+core@2.3.1 @@ -12547,6 +12549,14 @@ packages: stable: 0.1.8 dev: true + /swr/1.2.2_react@17.0.2: + resolution: {integrity: sha512-ky0BskS/V47GpW8d6RU7CPsr6J8cr7mQD6+do5eky3bM0IyJaoi3vO8UhvrzJaObuTlGhPl2szodeB2dUd76Xw==} + peerDependencies: + react: ^16.11.0 || ^17.0.0 || ^18.0.0 + dependencies: + react: 17.0.2 + dev: false + /symbol-tree/3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} dev: true