From 22a6278a9bc1959932d381860d7d2d36ebfc636f Mon Sep 17 00:00:00 2001 From: Xiao Yijun <xiaoyijun@silverhand.io> Date: Tue, 15 Mar 2022 12:42:42 +0800 Subject: [PATCH] feat(console): application details settings (#381) --- packages/console/src/App.tsx | 8 +- .../CopyToClipboard/index.module.scss | 1 + .../ApplicationDetails/index.module.scss | 31 +++++ .../src/pages/ApplicationDetails/index.tsx | 130 +++++++++++++++--- packages/phrases/src/locales/en.ts | 13 ++ packages/phrases/src/locales/zh-cn.ts | 13 ++ 6 files changed, 176 insertions(+), 20 deletions(-) diff --git a/packages/console/src/App.tsx b/packages/console/src/App.tsx index e58d797d9..e5fa00f8a 100644 --- a/packages/console/src/App.tsx +++ b/packages/console/src/App.tsx @@ -1,5 +1,5 @@ import React, { useEffect } from 'react'; -import { BrowserRouter, Route, Routes, useLocation, useNavigate } from 'react-router-dom'; +import { BrowserRouter, Navigate, Route, Routes, useLocation, useNavigate } from 'react-router-dom'; import { SWRConfig } from 'swr'; import './scss/normalized.scss'; @@ -44,7 +44,11 @@ const Main = () => { <Routes> <Route path="applications"> <Route index element={<Applications />} /> - <Route path=":id" element={<ApplicationDetails />} /> + <Route path=":id"> + <Route index element={<Navigate to="settings" />} /> + <Route path="settings" element={<ApplicationDetails />} /> + <Route path="advanced-settings" element={<ApplicationDetails />} /> + </Route> </Route> <Route path="api-resources"> <Route index element={<ApiResources />} /> diff --git a/packages/console/src/components/CopyToClipboard/index.module.scss b/packages/console/src/components/CopyToClipboard/index.module.scss index 8dc478d86..a84cf12a4 100644 --- a/packages/console/src/components/CopyToClipboard/index.module.scss +++ b/packages/console/src/components/CopyToClipboard/index.module.scss @@ -12,6 +12,7 @@ .row { display: flex; align-items: center; + justify-content: space-between; cursor: text; > svg { diff --git a/packages/console/src/pages/ApplicationDetails/index.module.scss b/packages/console/src/pages/ApplicationDetails/index.module.scss index 454ce5f07..78f62ca6d 100644 --- a/packages/console/src/pages/ApplicationDetails/index.module.scss +++ b/packages/console/src/pages/ApplicationDetails/index.module.scss @@ -59,3 +59,34 @@ } } } + +.container .body { + > :not(:first-child) { + margin-top: _.unit(6); + } + + .tabContent { + form { + >:not(:first-child) { + margin-top: _.unit(6); + } + + .fields { + border-bottom: 1px solid var(--color-border); + padding-bottom: _.unit(6); + + > div { + @include _.form-text-field; + } + + .copy { + @include _.form-text-field; + } + } + + .submit { + text-align: right; + } + } + } +} diff --git a/packages/console/src/pages/ApplicationDetails/index.tsx b/packages/console/src/pages/ApplicationDetails/index.tsx index c78101230..d06b45304 100644 --- a/packages/console/src/pages/ApplicationDetails/index.tsx +++ b/packages/console/src/pages/ApplicationDetails/index.tsx @@ -1,46 +1,140 @@ import { Application } from '@logto/schemas'; -import React from 'react'; +import React, { useEffect } from 'react'; +import { useForm } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; -import { useParams } from 'react-router-dom'; +import { useLocation, useParams } from 'react-router-dom'; import useSWR from 'swr'; import BackLink from '@/components/BackLink'; import Button from '@/components/Button'; import Card from '@/components/Card'; import CopyToClipboard from '@/components/CopyToClipboard'; +import FormField from '@/components/FormField'; import ImagePlaceholder from '@/components/ImagePlaceholder'; +import TabNav, { TabNavLink } from '@/components/TabNav'; +import TextInput from '@/components/TextInput'; import { RequestError } from '@/swr'; import { applicationTypeI18nKey } from '@/types/applications'; import * as styles from './index.module.scss'; +type OidcConfig = { + authorization_endpoint: string; + userinfo_endpoint: string; + token_endpoint: string; +}; + const ApplicationDetails = () => { const { id } = useParams(); + const location = useLocation(); + const { data, error } = useSWR<Application, RequestError>(id && `/api/applications/${id}`); + const { data: oidcConfig, error: fetchOidcConfigError } = useSWR<OidcConfig, RequestError>( + '/oidc/.well-known/openid-configuration' + ); + const isLoading = !data && !error && !fetchOidcConfigError; + const dataFetched = data && oidcConfig; + + const { handleSubmit, register, reset } = useForm<Application>({ + defaultValues: data, + }); + const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); - const isLoading = !data && !error; + + const isAdvancedSettings = location.pathname.includes('advanced-settings'); + + useEffect(() => { + if (!data) { + return; + } + reset(data); + }, [data, reset]); + + const onSubmit = handleSubmit((formData) => { + console.log(formData); + }); return ( <div className={styles.container}> <BackLink to="/applications">{t('application_details.back_to_applications')}</BackLink> {isLoading && <div>loading</div>} {error && <div>{`error occurred: ${error.metadata.code}`}</div>} - {data && ( - <Card className={styles.header}> - <ImagePlaceholder size={76} borderRadius={16} /> - <div className={styles.metadata}> - <div className={styles.name}>{data.name}</div> - <div> - <div className={styles.type}>{t(`${applicationTypeI18nKey[data.type]}.title`)}</div> - <div className={styles.verticalBar} /> - <div className={styles.text}>App ID</div> - <CopyToClipboard value={data.id} className={styles.copy} /> + {dataFetched && ( + <> + <Card className={styles.header}> + <ImagePlaceholder size={76} borderRadius={16} /> + <div className={styles.metadata}> + <div className={styles.name}>{data.name}</div> + <div> + <div className={styles.type}>{t(`${applicationTypeI18nKey[data.type]}.title`)}</div> + <div className={styles.verticalBar} /> + <div className={styles.text}>App ID</div> + <CopyToClipboard value={data.id} className={styles.copy} /> + </div> </div> - </div> - <div> - <Button title="admin_console.application_details.check_help_guide" /> - </div> - </Card> + <div> + <Button title="admin_console.application_details.check_help_guide" /> + </div> + </Card> + <Card className={styles.body}> + <TabNav> + <TabNavLink href={`/applications/${data.id}/settings`}> + {t('application_details.settings')} + </TabNavLink> + <TabNavLink href={`/applications/${data.id}/advanced-settings`}> + {t('application_details.advanced_settings')} + </TabNavLink> + </TabNav> + <div className={styles.tabContent}> + <form onSubmit={onSubmit}> + <div className={styles.fields}> + {!isAdvancedSettings && ( + <> + <FormField + isRequired + title="admin_console.application_details.application_name" + > + <TextInput {...register('name', { required: true })} /> + </FormField> + <FormField title="admin_console.application_details.description"> + <TextInput {...register('description')} /> + </FormField> + <FormField title="admin_console.application_details.authorization_endpoint"> + <CopyToClipboard + className={styles.copy} + value={oidcConfig.authorization_endpoint} + /> + </FormField> + </> + )} + {isAdvancedSettings && ( + <> + <FormField title="admin_console.application_details.token_endpoint"> + <CopyToClipboard + className={styles.copy} + value={oidcConfig.token_endpoint} + /> + </FormField> + <FormField title="admin_console.application_details.user_info_endpoint"> + <CopyToClipboard + className={styles.copy} + value={oidcConfig.userinfo_endpoint} + /> + </FormField> + </> + )} + </div> + <div className={styles.submit}> + <Button + htmlType="submit" + type="primary" + title="admin_console.application_details.save_changes" + /> + </div> + </form> + </div> + </Card> + </> )} </div> ); diff --git a/packages/phrases/src/locales/en.ts b/packages/phrases/src/locales/en.ts index ada5055ee..4d3bb86a1 100644 --- a/packages/phrases/src/locales/en.ts +++ b/packages/phrases/src/locales/en.ts @@ -80,6 +80,19 @@ const translation = { application_details: { back_to_applications: 'Back to Applications', check_help_guide: 'Check Help Guide', + settings: 'Settings', + advanced_settings: 'Advanced Settings', + application_name: 'Application Name', + description: 'Description', + authorization_endpoint: 'Authorization Endpiont', + redirect_uri: 'Redirect URI', + post_sign_out_redirect_uri: 'Post Sign Out Redirect URI', + add_another: 'Add another', + id_token_expiration: 'ID Token Expiration', + refresh_token_expiration: 'Refresh Token Expiration', + token_endpoint: 'Token Endpoint', + user_info_endpoint: 'User Info Endpoint', + save_changes: 'Save Changes', }, api_resources: { title: 'API Resources', diff --git a/packages/phrases/src/locales/zh-cn.ts b/packages/phrases/src/locales/zh-cn.ts index 1c74bbd33..1fd309fd9 100644 --- a/packages/phrases/src/locales/zh-cn.ts +++ b/packages/phrases/src/locales/zh-cn.ts @@ -82,6 +82,19 @@ const translation = { application_details: { back_to_applications: '返回应用集', check_help_guide: 'Check Help Guide', + settings: 'Settings', + advanced_settings: 'Advanced Settings', + application_name: 'Application Name', + description: 'Description', + authorization_endpoint: 'Authorization Endpiont', + redirect_uri: 'Redirect URI', + post_sign_out_redirect_uri: 'Post Sign Out Redirect URI', + add_another: 'Add another', + id_token_expiration: 'ID Token Expiration', + refresh_token_expiration: 'Refresh Token Expiration', + token_endpoint: 'Token Endpoint', + user_info_endpoint: 'User Info Endpoint', + save_changes: 'Save Changes', }, api_resources: { title: 'API Resources',