0
Fork 0
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:
Wang Sijie 2022-04-27 14:11:17 +08:00 committed by GitHub
parent 5e251bdc08
commit 19b9db809a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 154 additions and 103 deletions

View file

@ -44,8 +44,6 @@
} }
.body { .body {
padding-bottom: 0;
> :first-child { > :first-child {
margin-top: 0; margin-top: 0;
} }

View file

@ -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>
</> </>
)} )}

View file

@ -5,8 +5,6 @@
} }
.body { .body {
padding-bottom: 0;
> :first-child { > :first-child {
margin-top: 0; margin-top: 0;
} }

View file

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

View file

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

View file

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

View file

@ -0,0 +1,3 @@
.container {
padding-bottom: 0;
}

View file

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

View file

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

View file

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

View file

@ -57,8 +57,6 @@
} }
.body { .body {
padding-bottom: 0;
> :first-child { > :first-child {
margin-top: 0; margin-top: 0;
} }

View file

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

View file

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