mirror of
https://github.com/logto-io/logto.git
synced 2025-02-17 22:04:19 -05:00
feat(console): retrieve applications from api (#320)
* feat(console): retrieve applications from api * fix(console): i18n key * fix(console): update per review
This commit is contained in:
parent
06fd253754
commit
bb1d3c0a37
11 changed files with 116 additions and 31 deletions
|
@ -25,7 +25,8 @@
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-i18next": "^11.15.4",
|
"react-i18next": "^11.15.4",
|
||||||
"react-router-dom": "^6.2.2"
|
"react-router-dom": "^6.2.2",
|
||||||
|
"swr": "^1.2.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@parcel/core": "^2.3.1",
|
"@parcel/core": "^2.3.1",
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { BrowserRouter, Route, Routes, useLocation, useNavigate } from 'react-router-dom';
|
import { BrowserRouter, Route, Routes, useLocation, useNavigate } from 'react-router-dom';
|
||||||
|
import { SWRConfig } from 'swr';
|
||||||
import './scss/normalized.scss';
|
import './scss/normalized.scss';
|
||||||
|
|
||||||
import * as styles from './App.module.scss';
|
import * as styles from './App.module.scss';
|
||||||
import AppContent from './components/AppContent';
|
import AppContent from './components/AppContent';
|
||||||
import Content from './components/Content';
|
import Content from './components/Content';
|
||||||
|
@ -10,6 +11,7 @@ import Topbar from './components/Topbar';
|
||||||
import initI18n from './i18n/init';
|
import initI18n from './i18n/init';
|
||||||
import ApiResources from './pages/ApiResources';
|
import ApiResources from './pages/ApiResources';
|
||||||
import Applications from './pages/Applications';
|
import Applications from './pages/Applications';
|
||||||
|
import { fetcher } from './swr';
|
||||||
|
|
||||||
const isBasenameNeeded = process.env.NODE_ENV !== 'development' || process.env.PORT === '5002';
|
const isBasenameNeeded = process.env.NODE_ENV !== 'development' || process.env.PORT === '5002';
|
||||||
|
|
||||||
|
@ -26,18 +28,20 @@ const Main = () => {
|
||||||
}, [location.pathname, navigate]);
|
}, [location.pathname, navigate]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppContent theme="light">
|
<SWRConfig value={{ fetcher }}>
|
||||||
<Topbar />
|
<AppContent theme="light">
|
||||||
<div className={styles.content}>
|
<Topbar />
|
||||||
<Sidebar />
|
<div className={styles.content}>
|
||||||
<Content>
|
<Sidebar />
|
||||||
<Routes>
|
<Content>
|
||||||
<Route path="api-resources" element={<ApiResources />} />
|
<Routes>
|
||||||
<Route path="applications" element={<Applications />} />
|
<Route path="api-resources" element={<ApiResources />} />
|
||||||
</Routes>
|
<Route path="applications" element={<Applications />} />
|
||||||
</Content>
|
</Routes>
|
||||||
</div>
|
</Content>
|
||||||
</AppContent>
|
</div>
|
||||||
|
</AppContent>
|
||||||
|
</SWRConfig>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ const ItemPreview = ({ title, subtitle, icon }: Props) => {
|
||||||
{icon}
|
{icon}
|
||||||
<div>
|
<div>
|
||||||
<div className={styles.title}>{title}</div>
|
<div className={styles.title}>{title}</div>
|
||||||
{subtitle && <div className={styles.subtitle}>{subtitle}</div>}
|
{subtitle && <div className={styles.subtitle}>{String(subtitle)}</div>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
margin-top: _.unit(6);
|
margin-top: _.unit(6);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
tbody tr {
|
tbody tr.clickable {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
|
import { Application } from '@logto/schemas';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import useSWR from 'swr';
|
||||||
|
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
import Card from '@/components/Card';
|
import Card from '@/components/Card';
|
||||||
|
@ -6,10 +9,16 @@ import CardTitle from '@/components/CardTitle';
|
||||||
import CopyToClipboard from '@/components/CopyToClipboard';
|
import CopyToClipboard from '@/components/CopyToClipboard';
|
||||||
import ImagePlaceholder from '@/components/ImagePlaceholder';
|
import ImagePlaceholder from '@/components/ImagePlaceholder';
|
||||||
import ItemPreview from '@/components/ItemPreview';
|
import ItemPreview from '@/components/ItemPreview';
|
||||||
|
import { RequestError } from '@/swr';
|
||||||
|
import { applicationTypeI18nKey } from '@/types/applications';
|
||||||
|
|
||||||
import * as styles from './index.module.scss';
|
import * as styles from './index.module.scss';
|
||||||
|
|
||||||
const Applications = () => {
|
const Applications = () => {
|
||||||
|
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||||
|
const { data, error } = useSWR<Application[], RequestError>('/api/applications');
|
||||||
|
const isLoading = !data && !error;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<div className={styles.headline}>
|
<div className={styles.headline}>
|
||||||
|
@ -19,23 +28,35 @@ const Applications = () => {
|
||||||
<table className={styles.table}>
|
<table className={styles.table}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<td className={styles.applicationName}>Application Name</td>
|
<td className={styles.applicationName}>{t('applications.application_name')}</td>
|
||||||
<td>Client ID</td>
|
<td>{t('applications.client_id')}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
{error && (
|
||||||
<td>
|
<tr>
|
||||||
<ItemPreview
|
<td colSpan={2}>error occurred: {error.metadata.code}</td>
|
||||||
title="Default App"
|
</tr>
|
||||||
subtitle="Single Page Application"
|
)}
|
||||||
icon={<ImagePlaceholder />}
|
{isLoading && (
|
||||||
/>
|
<tr>
|
||||||
</td>
|
<td colSpan={2}>loading</td>
|
||||||
<td>
|
</tr>
|
||||||
<CopyToClipboard value="RUMatENw0rFWO5aGbMI8tY2Qol50eOg3" />
|
)}
|
||||||
</td>
|
{data?.map(({ id, name, type }) => (
|
||||||
</tr>
|
<tr key={id}>
|
||||||
|
<td>
|
||||||
|
<ItemPreview
|
||||||
|
title={name}
|
||||||
|
subtitle={String(t(applicationTypeI18nKey[type]))}
|
||||||
|
icon={<ImagePlaceholder />}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<CopyToClipboard value={id} />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
22
packages/console/src/swr/index.ts
Normal file
22
packages/console/src/swr/index.ts
Normal file
|
@ -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();
|
||||||
|
};
|
8
packages/console/src/types/applications.ts
Normal file
8
packages/console/src/types/applications.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import { AdminConsoleKey } from '@logto/phrases';
|
||||||
|
import { ApplicationType } from '@logto/schemas';
|
||||||
|
|
||||||
|
export const applicationTypeI18nKey: Record<ApplicationType, AdminConsoleKey> = {
|
||||||
|
[ApplicationType.Native]: 'applications.type.native',
|
||||||
|
[ApplicationType.SPA]: 'applications.type.spa',
|
||||||
|
[ApplicationType.Traditional]: 'applications.type.tranditional',
|
||||||
|
};
|
7
packages/core/src/include.d/koa.d.ts
vendored
7
packages/core/src/include.d/koa.d.ts
vendored
|
@ -1,4 +1,4 @@
|
||||||
import { DefaultState, DefaultContext, ParameterizedContext, Next } from 'koa';
|
import { DefaultState, DefaultContext, ParameterizedContext, Next, BaseRequest } from 'koa';
|
||||||
|
|
||||||
declare module 'koa' {
|
declare module 'koa' {
|
||||||
// Have to do this patch since `compose.Middleware` returns `any`.
|
// Have to do this patch since `compose.Middleware` returns `any`.
|
||||||
|
@ -10,4 +10,9 @@ declare module 'koa' {
|
||||||
ResponseBodyT = any,
|
ResponseBodyT = any,
|
||||||
NextT = void
|
NextT = void
|
||||||
> = KoaMiddleware<ParameterizedContext<StateT, ContextT, ResponseBodyT>, NextT>;
|
> = KoaMiddleware<ParameterizedContext<StateT, ContextT, ResponseBodyT>, NextT>;
|
||||||
|
|
||||||
|
interface Request extends BaseRequest {
|
||||||
|
body?: any;
|
||||||
|
files?: Files;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,13 @@ const translation = {
|
||||||
subtitle:
|
subtitle:
|
||||||
'Setup a mobile, single page or traditional application to use Logto for authentication.',
|
'Setup a mobile, single page or traditional application to use Logto for authentication.',
|
||||||
create: 'Create Application',
|
create: 'Create Application',
|
||||||
|
application_name: 'Application Name',
|
||||||
|
client_id: 'Client ID',
|
||||||
|
type: {
|
||||||
|
native: 'Native App',
|
||||||
|
spa: 'Single Page App',
|
||||||
|
tranditional: 'Tranditional Web App',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -40,6 +40,13 @@ const translation = {
|
||||||
subtitle:
|
subtitle:
|
||||||
'Setup a mobile, single page or traditional application to use Logto for authentication.',
|
'Setup a mobile, single page or traditional application to use Logto for authentication.',
|
||||||
create: 'Create Application',
|
create: 'Create Application',
|
||||||
|
application_name: 'Application Name',
|
||||||
|
client_id: 'Client ID',
|
||||||
|
type: {
|
||||||
|
native: 'Native App',
|
||||||
|
spa: 'Single Page App',
|
||||||
|
tranditional: 'Tranditional Web App',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
10
pnpm-lock.yaml
generated
10
pnpm-lock.yaml
generated
|
@ -46,6 +46,7 @@ importers:
|
||||||
react-i18next: ^11.15.4
|
react-i18next: ^11.15.4
|
||||||
react-router-dom: ^6.2.2
|
react-router-dom: ^6.2.2
|
||||||
stylelint: ^13.13.1
|
stylelint: ^13.13.1
|
||||||
|
swr: ^1.2.2
|
||||||
typescript: ^4.5.5
|
typescript: ^4.5.5
|
||||||
dependencies:
|
dependencies:
|
||||||
'@logto/phrases': link:../phrases
|
'@logto/phrases': link:../phrases
|
||||||
|
@ -58,6 +59,7 @@ importers:
|
||||||
react-dom: 17.0.2_react@17.0.2
|
react-dom: 17.0.2_react@17.0.2
|
||||||
react-i18next: 11.15.4_2c37a602a29bb6bd53f3de707a8cfcc5
|
react-i18next: 11.15.4_2c37a602a29bb6bd53f3de707a8cfcc5
|
||||||
react-router-dom: 6.2.2_react-dom@17.0.2+react@17.0.2
|
react-router-dom: 6.2.2_react-dom@17.0.2+react@17.0.2
|
||||||
|
swr: 1.2.2_react@17.0.2
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@parcel/core': 2.3.1
|
'@parcel/core': 2.3.1
|
||||||
'@parcel/transformer-sass': 2.3.1_@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
|
stable: 0.1.8
|
||||||
dev: true
|
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:
|
/symbol-tree/3.2.4:
|
||||||
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
|
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
Loading…
Add table
Reference in a new issue