0
Fork 0
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:
Gao Sun 2022-03-04 17:26:34 +08:00 committed by GitHub
parent 06fd253754
commit bb1d3c0a37
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 116 additions and 31 deletions

View file

@ -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",

View file

@ -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 (
<AppContent theme="light">
<Topbar />
<div className={styles.content}>
<Sidebar />
<Content>
<Routes>
<Route path="api-resources" element={<ApiResources />} />
<Route path="applications" element={<Applications />} />
</Routes>
</Content>
</div>
</AppContent>
<SWRConfig value={{ fetcher }}>
<AppContent theme="light">
<Topbar />
<div className={styles.content}>
<Sidebar />
<Content>
<Routes>
<Route path="api-resources" element={<ApiResources />} />
<Route path="applications" element={<Applications />} />
</Routes>
</Content>
</div>
</AppContent>
</SWRConfig>
);
};

View file

@ -14,7 +14,7 @@ const ItemPreview = ({ title, subtitle, icon }: Props) => {
{icon}
<div>
<div className={styles.title}>{title}</div>
{subtitle && <div className={styles.subtitle}>{subtitle}</div>}
{subtitle && <div className={styles.subtitle}>{String(subtitle)}</div>}
</div>
</div>
);

View file

@ -9,7 +9,7 @@
margin-top: _.unit(6);
width: 100%;
tbody tr {
tbody tr.clickable {
cursor: pointer;
&:hover {

View file

@ -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<Application[], RequestError>('/api/applications');
const isLoading = !data && !error;
return (
<Card>
<div className={styles.headline}>
@ -19,23 +28,35 @@ const Applications = () => {
<table className={styles.table}>
<thead>
<tr>
<td className={styles.applicationName}>Application Name</td>
<td>Client ID</td>
<td className={styles.applicationName}>{t('applications.application_name')}</td>
<td>{t('applications.client_id')}</td>
</tr>
</thead>
<tbody>
<tr>
<td>
<ItemPreview
title="Default App"
subtitle="Single Page Application"
icon={<ImagePlaceholder />}
/>
</td>
<td>
<CopyToClipboard value="RUMatENw0rFWO5aGbMI8tY2Qol50eOg3" />
</td>
</tr>
{error && (
<tr>
<td colSpan={2}>error occurred: {error.metadata.code}</td>
</tr>
)}
{isLoading && (
<tr>
<td colSpan={2}>loading</td>
</tr>
)}
{data?.map(({ id, name, type }) => (
<tr key={id}>
<td>
<ItemPreview
title={name}
subtitle={String(t(applicationTypeI18nKey[type]))}
icon={<ImagePlaceholder />}
/>
</td>
<td>
<CopyToClipboard value={id} />
</td>
</tr>
))}
</tbody>
</table>
</Card>

View 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();
};

View 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',
};

View file

@ -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<ParameterizedContext<StateT, ContextT, ResponseBodyT>, NextT>;
interface Request extends BaseRequest {
body?: any;
files?: Files;
}
}

View file

@ -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',
},
},
},
};

View file

@ -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',
},
},
},
};

10
pnpm-lock.yaml generated
View file

@ -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