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