mirror of
https://github.com/logto-io/logto.git
synced 2025-03-24 22:41:28 -05:00
fix(console): new ui in save changes footer (#661)
This commit is contained in:
parent
5e251bdc08
commit
19b9db809a
13 changed files with 154 additions and 103 deletions
|
@ -44,8 +44,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.body {
|
.body {
|
||||||
padding-bottom: 0;
|
|
||||||
|
|
||||||
> :first-child {
|
> :first-child {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { Resource } from '@logto/schemas';
|
import { Resource } from '@logto/schemas';
|
||||||
|
import classNames from 'classnames';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
import { toast } from 'react-hot-toast';
|
||||||
|
@ -141,11 +142,11 @@ const ApiResourceDetails = () => {
|
||||||
</Modal>
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
<Card className={styles.body}>
|
<Card className={classNames(styles.body, detailsStyles.body)}>
|
||||||
<TabNav>
|
<TabNav>
|
||||||
<TabNavLink href={location.pathname}>{t('api_resource_details.settings')}</TabNavLink>
|
<TabNavLink href={location.pathname}>{t('api_resource_details.settings')}</TabNavLink>
|
||||||
</TabNav>
|
</TabNav>
|
||||||
<form className={styles.form} onSubmit={onSubmit}>
|
<form className={classNames(styles.form, detailsStyles.body)} onSubmit={onSubmit}>
|
||||||
<div className={styles.fields}>
|
<div className={styles.fields}>
|
||||||
<FormField
|
<FormField
|
||||||
isRequired
|
isRequired
|
||||||
|
@ -164,7 +165,9 @@ const ApiResourceDetails = () => {
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
</div>
|
</div>
|
||||||
<div className={detailsStyles.footer}>
|
</form>
|
||||||
|
<div className={detailsStyles.footer}>
|
||||||
|
<div className={detailsStyles.footerMain}>
|
||||||
<Button
|
<Button
|
||||||
isLoading={isSubmitting}
|
isLoading={isSubmitting}
|
||||||
htmlType="submit"
|
htmlType="submit"
|
||||||
|
@ -173,7 +176,7 @@ const ApiResourceDetails = () => {
|
||||||
size="large"
|
size="large"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -5,8 +5,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.body {
|
.body {
|
||||||
padding-bottom: 0;
|
|
||||||
|
|
||||||
> :first-child {
|
> :first-child {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { Application, SnakeCaseOidcConfig } from '@logto/schemas';
|
import { Application, SnakeCaseOidcConfig } from '@logto/schemas';
|
||||||
|
import classNames from 'classnames';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Controller, useForm } from 'react-hook-form';
|
import { Controller, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
import { toast } from 'react-hot-toast';
|
||||||
|
@ -246,7 +247,7 @@ const ApplicationDetails = () => {
|
||||||
</Modal>
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
<Card className={styles.body}>
|
<Card className={classNames(styles.body, detailsStyles.body)}>
|
||||||
<TabNav>
|
<TabNav>
|
||||||
<TabNavLink href={`/applications/${data.id}/settings`}>
|
<TabNavLink href={`/applications/${data.id}/settings`}>
|
||||||
{t('application_details.settings')}
|
{t('application_details.settings')}
|
||||||
|
@ -260,13 +261,15 @@ const ApplicationDetails = () => {
|
||||||
{isAdvancedSettings ? AdvancedSettingsPage : SettingsPage}
|
{isAdvancedSettings ? AdvancedSettingsPage : SettingsPage}
|
||||||
</div>
|
</div>
|
||||||
<div className={detailsStyles.footer}>
|
<div className={detailsStyles.footer}>
|
||||||
<Button
|
<div className={detailsStyles.footerMain}>
|
||||||
isLoading={isSubmitting}
|
<Button
|
||||||
htmlType="submit"
|
isLoading={isSubmitting}
|
||||||
type="primary"
|
htmlType="submit"
|
||||||
size="large"
|
type="primary"
|
||||||
title="admin_console.application_details.save_changes"
|
size="large"
|
||||||
/>
|
title="admin_console.application_details.save_changes"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { ConnectorDTO, ConnectorType } from '@logto/schemas';
|
import { ConnectorDTO, ConnectorType } from '@logto/schemas';
|
||||||
|
import classNames from 'classnames';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { toast } from 'react-hot-toast';
|
import { toast } from 'react-hot-toast';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
@ -179,7 +180,7 @@ const ConnectorDetails = () => {
|
||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
{data && (
|
{data && (
|
||||||
<Card className={styles.body}>
|
<Card className={classNames(styles.body, detailsStyles.body)}>
|
||||||
<TabNav>
|
<TabNav>
|
||||||
<TabNavLink href={`/connectors/${connectorId ?? ''}`}>
|
<TabNavLink href={`/connectors/${connectorId ?? ''}`}>
|
||||||
{t('connector_details.tab_settings')}
|
{t('connector_details.tab_settings')}
|
||||||
|
@ -197,12 +198,14 @@ const ConnectorDetails = () => {
|
||||||
)}
|
)}
|
||||||
{saveError && <div>{saveError}</div>}
|
{saveError && <div>{saveError}</div>}
|
||||||
<div className={detailsStyles.footer}>
|
<div className={detailsStyles.footer}>
|
||||||
<Button
|
<div className={detailsStyles.footerMain}>
|
||||||
type="primary"
|
<Button
|
||||||
title="admin_console.connector_details.save_changes"
|
type="primary"
|
||||||
isLoading={isSubmitting}
|
title="admin_console.connector_details.save_changes"
|
||||||
onClick={handleSave}
|
isLoading={isSubmitting}
|
||||||
/>
|
onClick={handleSave}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -78,7 +78,12 @@ const CreateForm = ({ onClose, isOpen: isFormOpen, type }: Props) => {
|
||||||
{isLoading && 'Loading...'}
|
{isLoading && 'Loading...'}
|
||||||
{error && error}
|
{error && error}
|
||||||
{connectors && (
|
{connectors && (
|
||||||
<RadioGroup name="connector" value={activeConnectorId} onChange={setActiveConnectorId}>
|
<RadioGroup
|
||||||
|
name="connector"
|
||||||
|
value={activeConnectorId}
|
||||||
|
type="card"
|
||||||
|
onChange={setActiveConnectorId}
|
||||||
|
>
|
||||||
{connectors.map(({ id, metadata: { name, logo, description } }) => (
|
{connectors.map(({ id, metadata: { name, logo, description } }) => (
|
||||||
<Radio key={id} value={id} className={styles.connector}>
|
<Radio key={id} value={id} className={styles.connector}>
|
||||||
<div className={styles.logo}>
|
<div className={styles.logo}>
|
||||||
|
|
3
packages/console/src/pages/Settings/index.module.scss
Normal file
3
packages/console/src/pages/Settings/index.module.scss
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
.container {
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
import { Language } from '@logto/phrases';
|
import { Language } from '@logto/phrases';
|
||||||
import { AppearanceMode, Setting } from '@logto/schemas';
|
import { AppearanceMode, Setting } from '@logto/schemas';
|
||||||
|
import classNames from 'classnames';
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { Controller, useForm } from 'react-hook-form';
|
import { Controller, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
import { toast } from 'react-hot-toast';
|
||||||
|
@ -16,6 +17,8 @@ import TextInput from '@/components/TextInput';
|
||||||
import useApi, { RequestError } from '@/hooks/use-api';
|
import useApi, { RequestError } from '@/hooks/use-api';
|
||||||
import * as detailsStyles from '@/scss/details.module.scss';
|
import * as detailsStyles from '@/scss/details.module.scss';
|
||||||
|
|
||||||
|
import * as styles from './index.module.scss';
|
||||||
|
|
||||||
const Settings = () => {
|
const Settings = () => {
|
||||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||||
const { data, error, mutate } = useSWR<Setting, RequestError>('/api/settings');
|
const { data, error, mutate } = useSWR<Setting, RequestError>('/api/settings');
|
||||||
|
@ -51,7 +54,7 @@ const Settings = () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className={detailsStyles.container}>
|
<Card className={classNames(detailsStyles.container, styles.container)}>
|
||||||
<CardTitle title="settings.title" subtitle="settings.description" />
|
<CardTitle title="settings.title" subtitle="settings.description" />
|
||||||
<TabNav>
|
<TabNav>
|
||||||
<TabNavLink href="/settings">{t('settings.tabs.general')}</TabNavLink>
|
<TabNavLink href="/settings">{t('settings.tabs.general')}</TabNavLink>
|
||||||
|
@ -59,67 +62,71 @@ const Settings = () => {
|
||||||
{!data && !error && <div>loading</div>}
|
{!data && !error && <div>loading</div>}
|
||||||
{error && <div>{`error occurred: ${error.body.message}`}</div>}
|
{error && <div>{`error occurred: ${error.body.message}`}</div>}
|
||||||
{data && (
|
{data && (
|
||||||
<form onSubmit={onSubmit}>
|
<>
|
||||||
<FormField title="admin_console.settings.custom_domain">
|
<form className={detailsStyles.body} onSubmit={onSubmit}>
|
||||||
<TextInput {...register('customDomain')} />
|
<FormField title="admin_console.settings.custom_domain">
|
||||||
</FormField>
|
<TextInput {...register('customDomain')} />
|
||||||
<FormField isRequired title="admin_console.settings.language">
|
</FormField>
|
||||||
<Controller
|
<FormField isRequired title="admin_console.settings.language">
|
||||||
name="adminConsole.language"
|
<Controller
|
||||||
control={control}
|
name="adminConsole.language"
|
||||||
render={({ field: { value, onChange } }) => (
|
control={control}
|
||||||
<Select
|
render={({ field: { value, onChange } }) => (
|
||||||
value={value}
|
<Select
|
||||||
options={[
|
value={value}
|
||||||
{
|
options={[
|
||||||
value: Language.English,
|
{
|
||||||
title: t('settings.language_english'),
|
value: Language.English,
|
||||||
},
|
title: t('settings.language_english'),
|
||||||
{
|
},
|
||||||
value: Language.Chinese,
|
{
|
||||||
title: t('settings.language_chinese'),
|
value: Language.Chinese,
|
||||||
},
|
title: t('settings.language_chinese'),
|
||||||
]}
|
},
|
||||||
onChange={onChange}
|
]}
|
||||||
/>
|
onChange={onChange}
|
||||||
)}
|
/>
|
||||||
/>
|
)}
|
||||||
</FormField>
|
/>
|
||||||
<FormField isRequired title="admin_console.settings.appearance">
|
</FormField>
|
||||||
<Controller
|
<FormField isRequired title="admin_console.settings.appearance">
|
||||||
name="adminConsole.appearanceMode"
|
<Controller
|
||||||
control={control}
|
name="adminConsole.appearanceMode"
|
||||||
render={({ field: { value, onChange } }) => (
|
control={control}
|
||||||
<Select
|
render={({ field: { value, onChange } }) => (
|
||||||
value={value}
|
<Select
|
||||||
options={[
|
value={value}
|
||||||
{
|
options={[
|
||||||
value: AppearanceMode.SyncWithSystem,
|
{
|
||||||
title: t('settings.appearance_system'),
|
value: AppearanceMode.SyncWithSystem,
|
||||||
},
|
title: t('settings.appearance_system'),
|
||||||
{
|
},
|
||||||
value: AppearanceMode.LightMode,
|
{
|
||||||
title: t('settings.appearance_light'),
|
value: AppearanceMode.LightMode,
|
||||||
},
|
title: t('settings.appearance_light'),
|
||||||
{
|
},
|
||||||
value: AppearanceMode.DarkMode,
|
{
|
||||||
title: t('settings.appearance_dark'),
|
value: AppearanceMode.DarkMode,
|
||||||
},
|
title: t('settings.appearance_dark'),
|
||||||
]}
|
},
|
||||||
onChange={onChange}
|
]}
|
||||||
/>
|
onChange={onChange}
|
||||||
)}
|
/>
|
||||||
/>
|
)}
|
||||||
</FormField>
|
/>
|
||||||
|
</FormField>
|
||||||
|
</form>
|
||||||
<div className={detailsStyles.footer}>
|
<div className={detailsStyles.footer}>
|
||||||
<Button
|
<div className={detailsStyles.footerMain}>
|
||||||
isLoading={isSubmitting}
|
<Button
|
||||||
type="primary"
|
isLoading={isSubmitting}
|
||||||
htmlType="submit"
|
type="primary"
|
||||||
title="general.save_changes"
|
htmlType="submit"
|
||||||
/>
|
title="general.save_changes"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</>
|
||||||
)}
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|
|
@ -13,6 +13,14 @@
|
||||||
.tabs {
|
.tabs {
|
||||||
padding-top: _.unit(2);
|
padding-top: _.unit(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form {
|
||||||
|
padding-bottom: _.unit(8);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview {
|
.preview {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { SignInExperience as SignInExperienceType } from '@logto/schemas';
|
import { SignInExperience as SignInExperienceType } from '@logto/schemas';
|
||||||
|
import classNames from 'classnames';
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
import { toast } from 'react-hot-toast';
|
||||||
|
@ -55,8 +56,8 @@ const SignInExperience = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.wrapper}>
|
<div className={styles.wrapper}>
|
||||||
<div className={styles.setup}>
|
<div className={classNames(styles.setup, detailsStyles.container)}>
|
||||||
<Card className={detailsStyles.container}>
|
<Card className={styles.card}>
|
||||||
<CardTitle title="sign_in_exp.title" subtitle="sign_in_exp.description" />
|
<CardTitle title="sign_in_exp.title" subtitle="sign_in_exp.description" />
|
||||||
<TabNav className={styles.tabs}>
|
<TabNav className={styles.tabs}>
|
||||||
<TabNavLink href="/sign-in-experience/experience">
|
<TabNavLink href="/sign-in-experience/experience">
|
||||||
|
@ -73,7 +74,7 @@ const SignInExperience = () => {
|
||||||
{error && <div>{`error occurred: ${error.body.message}`}</div>}
|
{error && <div>{`error occurred: ${error.body.message}`}</div>}
|
||||||
{data && (
|
{data && (
|
||||||
<FormProvider {...methods}>
|
<FormProvider {...methods}>
|
||||||
<form onSubmit={onSubmit}>
|
<form className={classNames(detailsStyles.body, styles.form)} onSubmit={onSubmit}>
|
||||||
{tab === 'experience' && (
|
{tab === 'experience' && (
|
||||||
<>
|
<>
|
||||||
<BrandingForm />
|
<BrandingForm />
|
||||||
|
@ -82,7 +83,9 @@ const SignInExperience = () => {
|
||||||
)}
|
)}
|
||||||
{tab === 'methods' && <SignInMethodsForm />}
|
{tab === 'methods' && <SignInMethodsForm />}
|
||||||
{tab === 'others' && <LanguagesForm />}
|
{tab === 'others' && <LanguagesForm />}
|
||||||
<div className={detailsStyles.footer}>
|
</form>
|
||||||
|
<div className={detailsStyles.footer}>
|
||||||
|
<div className={detailsStyles.footerMain}>
|
||||||
<Button
|
<Button
|
||||||
isLoading={isSubmitting}
|
isLoading={isSubmitting}
|
||||||
type="primary"
|
type="primary"
|
||||||
|
@ -90,7 +93,7 @@ const SignInExperience = () => {
|
||||||
title="general.save_changes"
|
title="general.save_changes"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</div>
|
||||||
</FormProvider>
|
</FormProvider>
|
||||||
)}
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
|
|
|
@ -57,8 +57,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.body {
|
.body {
|
||||||
padding-bottom: 0;
|
|
||||||
|
|
||||||
> :first-child {
|
> :first-child {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { User } from '@logto/schemas';
|
import { User } from '@logto/schemas';
|
||||||
import { Nullable } from '@silverhand/essentials';
|
import { Nullable } from '@silverhand/essentials';
|
||||||
|
import classNames from 'classnames';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Controller, useController, useForm } from 'react-hook-form';
|
import { Controller, useController, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
import { toast } from 'react-hot-toast';
|
||||||
|
@ -176,7 +177,7 @@ const UserDetails = () => {
|
||||||
</ReactModal>
|
</ReactModal>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
<Card className={styles.body}>
|
<Card className={classNames(styles.body, detailsStyles.body)}>
|
||||||
<TabNav>
|
<TabNav>
|
||||||
<TabNavLink href={`/users/${id}`}>{t('user_details.tab_settings')}</TabNavLink>
|
<TabNavLink href={`/users/${id}`}>{t('user_details.tab_settings')}</TabNavLink>
|
||||||
<TabNavLink href={`/users/${id}/logs`}>{t('user_details.tab_logs')}</TabNavLink>
|
<TabNavLink href={`/users/${id}/logs`}>{t('user_details.tab_logs')}</TabNavLink>
|
||||||
|
@ -252,13 +253,15 @@ const UserDetails = () => {
|
||||||
</FormField>
|
</FormField>
|
||||||
</div>
|
</div>
|
||||||
<div className={detailsStyles.footer}>
|
<div className={detailsStyles.footer}>
|
||||||
<Button
|
<div className={detailsStyles.footerMain}>
|
||||||
isLoading={isSubmitting}
|
<Button
|
||||||
htmlType="submit"
|
isLoading={isSubmitting}
|
||||||
type="primary"
|
htmlType="submit"
|
||||||
title="admin_console.user_details.save_changes"
|
type="primary"
|
||||||
size="large"
|
title="admin_console.user_details.save_changes"
|
||||||
/>
|
size="large"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
|
@ -1,18 +1,37 @@
|
||||||
@use '@/scss/underscore' as _;
|
@use '@/scss/underscore' as _;
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
position: relative;
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
> *:not(:first-child) {
|
> *:not(:first-child) {
|
||||||
margin-top: _.unit(4);
|
margin-top: _.unit(4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.body {
|
||||||
|
position: relative;
|
||||||
|
padding-bottom: 0;
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
padding: 0 _.unit(6) _.unit(6);
|
|
||||||
text-align: right;
|
text-align: right;
|
||||||
position: sticky;
|
position: sticky;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
background: var(--color-layer-1);
|
margin: 0 _.unit(-6);
|
||||||
|
// Use the same color with app's background to cover card body
|
||||||
|
// simulate the always-on border-radius
|
||||||
|
background: var(--color-base);
|
||||||
|
|
||||||
|
.footerMain {
|
||||||
|
border-bottom-left-radius: 16px;
|
||||||
|
border-bottom-right-radius: 16px;
|
||||||
|
background: var(--color-layer-1);
|
||||||
|
padding: _.unit(6);
|
||||||
|
}
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
content: '';
|
content: '';
|
||||||
|
@ -20,7 +39,7 @@
|
||||||
opacity: 50%;
|
opacity: 50%;
|
||||||
height: 1px;
|
height: 1px;
|
||||||
display: block;
|
display: block;
|
||||||
margin: _.unit(6) _.unit(-6);
|
margin: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue