mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
Merge pull request #539 from logto-io/sijie--log-1729-experience
feat(console): add experience form fields
This commit is contained in:
commit
a71e0dfdb6
7 changed files with 201 additions and 24 deletions
|
@ -0,0 +1,70 @@
|
|||
import { BrandingStyle, SignInExperience } from '@logto/schemas';
|
||||
import React from 'react';
|
||||
import { Control, Controller, UseFormRegister, UseFormWatch } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import FormField from '@/components/FormField';
|
||||
import RadioGroup, { Radio } from '@/components/RadioGroup';
|
||||
import TextInput from '@/components/TextInput';
|
||||
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
type Props = {
|
||||
register: UseFormRegister<SignInExperience>;
|
||||
control: Control<SignInExperience>;
|
||||
watch: UseFormWatch<SignInExperience>;
|
||||
};
|
||||
|
||||
const BrandingForm = ({ register, control, watch }: Props) => {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
|
||||
const isDarkModeEnabled = watch('branding.isDarkModeEnabled');
|
||||
const style = watch('branding.style');
|
||||
const isSloganRequired = style === BrandingStyle.Logo_Slogan;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles.title}>{t('sign_in_exp.branding.title')}</div>
|
||||
<FormField isRequired title="admin_console.sign_in_exp.branding.primary_color">
|
||||
{/* TODO: LOG-1733 color-picker */}
|
||||
<TextInput {...register('branding.primaryColor', { required: true })} />
|
||||
</FormField>
|
||||
<FormField isRequired title="admin_console.sign_in_exp.branding.dark_mode">
|
||||
{/* TODO: LOG-2152 switch */}
|
||||
<TextInput {...register('branding.isDarkModeEnabled', { required: true })} />
|
||||
<div>{t('sign_in_exp.branding.dark_mode_description')}</div>
|
||||
</FormField>
|
||||
<FormField
|
||||
isRequired={isDarkModeEnabled}
|
||||
title="admin_console.sign_in_exp.branding.dark_primary_color"
|
||||
>
|
||||
{/* TODO: LOG-1733 color-picker */}
|
||||
<TextInput {...register('branding.darkPrimaryColor', { required: isDarkModeEnabled })} />
|
||||
</FormField>
|
||||
<FormField isRequired title="admin_console.sign_in_exp.branding.ui_style">
|
||||
{/* TODO: LOG-2153 plain radio */}
|
||||
<Controller
|
||||
name="branding.style"
|
||||
control={control}
|
||||
defaultValue={BrandingStyle.Logo_Slogan}
|
||||
render={({ field: { onChange, value, name } }) => (
|
||||
<RadioGroup value={value} name={name} onChange={onChange}>
|
||||
<Radio value={BrandingStyle.Logo_Slogan}>
|
||||
{t('sign_in_exp.branding.styles.logo_slogan')}
|
||||
</Radio>
|
||||
<Radio value={BrandingStyle.Logo}>{t('sign_in_exp.branding.styles.logo')}</Radio>
|
||||
</RadioGroup>
|
||||
)}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField isRequired title="admin_console.sign_in_exp.branding.logo_image_url">
|
||||
<TextInput {...register('branding.logoUrl', { required: true })} />
|
||||
</FormField>
|
||||
<FormField isRequired={isSloganRequired} title="admin_console.sign_in_exp.branding.slogan">
|
||||
<TextInput {...register('branding.slogan', { required: isSloganRequired })} />
|
||||
</FormField>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default BrandingForm;
|
|
@ -0,0 +1,34 @@
|
|||
import { SignInExperience } from '@logto/schemas';
|
||||
import React from 'react';
|
||||
import { UseFormRegister, UseFormWatch } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import FormField from '@/components/FormField';
|
||||
import TextInput from '@/components/TextInput';
|
||||
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
type Props = {
|
||||
register: UseFormRegister<SignInExperience>;
|
||||
watch: UseFormWatch<SignInExperience>;
|
||||
};
|
||||
|
||||
const TermsForm = ({ register, watch }: Props) => {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
const enabled = watch('termsOfUse.enabled');
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles.title}>{t('sign_in_exp.terms_of_use.title')}</div>
|
||||
<FormField isRequired title="admin_console.sign_in_exp.terms_of_use.enable">
|
||||
<TextInput {...register('termsOfUse.enabled', { required: true })} />
|
||||
<div>{t('sign_in_exp.terms_of_use.description')}</div>
|
||||
</FormField>
|
||||
<FormField isRequired={enabled} title="admin_console.sign_in_exp.terms_of_use.terms_of_use">
|
||||
<TextInput {...register('termsOfUse.contentUrl', { required: enabled })} />
|
||||
</FormField>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default TermsForm;
|
|
@ -0,0 +1,7 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
.title {
|
||||
color: var(--color-neutral-variant-60);
|
||||
font: var(--font-subhead-cap);
|
||||
margin-top: _.unit(8);
|
||||
}
|
|
@ -7,6 +7,8 @@
|
|||
.setup {
|
||||
flex: 1;
|
||||
margin-right: _.unit(3);
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
|
||||
.tabs {
|
||||
padding-top: _.unit(2);
|
||||
|
|
|
@ -1,36 +1,94 @@
|
|||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
import { SignInExperience as SignInExperienceType } from '@logto/schemas';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import useSWR from 'swr';
|
||||
|
||||
import Button from '@/components/Button';
|
||||
import Card from '@/components/Card';
|
||||
import CardTitle from '@/components/CardTitle';
|
||||
import TabNav, { TabNavLink } from '@/components/TabNav';
|
||||
import useApi, { RequestError } from '@/hooks/use-api';
|
||||
import * as detailsStyles from '@/scss/details.module.scss';
|
||||
|
||||
import BrandingForm from './components/BrandingForm';
|
||||
import TermsForm from './components/TermsForm';
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
const SignInExperience = () => {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
const { tab } = useParams();
|
||||
const { data, error, mutate } = useSWR<SignInExperienceType, RequestError>('/api/sign-in-exp');
|
||||
const {
|
||||
reset,
|
||||
control,
|
||||
handleSubmit,
|
||||
register,
|
||||
watch,
|
||||
formState: { isSubmitting },
|
||||
} = useForm<SignInExperienceType>();
|
||||
const api = useApi();
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
reset(data);
|
||||
}
|
||||
}, [data, reset]);
|
||||
|
||||
const onSubmit = handleSubmit(async (formData) => {
|
||||
if (!data || isSubmitting) {
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedData = await api
|
||||
.patch(`/api/applications/${data.id}`, {
|
||||
json: {
|
||||
...formData,
|
||||
},
|
||||
})
|
||||
.json<SignInExperienceType>();
|
||||
void mutate(updatedData);
|
||||
toast.success(t('application_details.save_success'));
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<Card className={classNames(detailsStyles.container, styles.setup)}>
|
||||
<CardTitle title="sign_in_exp.title" subtitle="sign_in_exp.description" />
|
||||
<TabNav className={styles.tabs}>
|
||||
<TabNavLink href="/sign-in-experience/experience">
|
||||
{t('sign_in_exp.tabs.experience')}
|
||||
</TabNavLink>
|
||||
<TabNavLink href="/sign-in-experience/methods">
|
||||
{t('sign_in_exp.tabs.methods')}
|
||||
</TabNavLink>
|
||||
<TabNavLink href="/sign-in-experience/others">{t('sign_in_exp.tabs.others')}</TabNavLink>
|
||||
</TabNav>
|
||||
<div>TODO</div>
|
||||
<div className={detailsStyles.footer}>
|
||||
<Button type="primary" title="general.save_changes" />
|
||||
</div>
|
||||
</Card>
|
||||
<div className={styles.setup}>
|
||||
<Card className={detailsStyles.container}>
|
||||
<CardTitle title="sign_in_exp.title" subtitle="sign_in_exp.description" />
|
||||
<TabNav className={styles.tabs}>
|
||||
<TabNavLink href="/sign-in-experience/experience">
|
||||
{t('sign_in_exp.tabs.experience')}
|
||||
</TabNavLink>
|
||||
<TabNavLink href="/sign-in-experience/methods">
|
||||
{t('sign_in_exp.tabs.methods')}
|
||||
</TabNavLink>
|
||||
<TabNavLink href="/sign-in-experience/others">
|
||||
{t('sign_in_exp.tabs.others')}
|
||||
</TabNavLink>
|
||||
</TabNav>
|
||||
{!data && !error && <div>loading</div>}
|
||||
{error && <div>{`error occurred: ${error.body.message}`}</div>}
|
||||
{data && (
|
||||
<form onSubmit={onSubmit}>
|
||||
{tab === 'experience' && (
|
||||
<BrandingForm register={register} control={control} watch={watch} />
|
||||
)}
|
||||
{tab === 'experience' && <TermsForm register={register} watch={watch} />}
|
||||
<div className={detailsStyles.footer}>
|
||||
<Button
|
||||
disabled={isSubmitting}
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
title="general.save_changes"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
<Card className={styles.preview}>TODO</Card>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -328,12 +328,15 @@ const translation = {
|
|||
branding: {
|
||||
title: 'BRANDING',
|
||||
primary_color: 'Primary color',
|
||||
dark_mode: 'Enable dark mode',
|
||||
dark_primary_color: 'Primary color (Dark)',
|
||||
dark_mode: 'Enable Dark Mode',
|
||||
dark_mode_description:
|
||||
'Enabling this setting will auto generate the dark mode color. You app won’t have dark mode if it’s turned off.',
|
||||
ui_style: 'Define your UI style',
|
||||
app_logo: 'App Logo',
|
||||
text: 'Text',
|
||||
styles: {
|
||||
logo_slogan: 'Logo + slogan',
|
||||
logo: 'Logo',
|
||||
},
|
||||
logo_image_url: 'Logo image URL',
|
||||
slogan: 'Slogan',
|
||||
slogan_placeholder: 'Unleash your creativity',
|
||||
|
|
|
@ -324,12 +324,15 @@ const translation = {
|
|||
branding: {
|
||||
title: 'BRANDING',
|
||||
primary_color: 'Primary color',
|
||||
dark_mode: 'Enable dark mode',
|
||||
dark_primary_color: 'Primary color (Dark)',
|
||||
dark_mode: 'Enable Dark Mode',
|
||||
dark_mode_description:
|
||||
'Enabling this setting will auto generate the dark mode color. You app won’t have dark mode if it’s turned off.',
|
||||
ui_style: 'Define your UI style',
|
||||
app_logo: 'App Logo',
|
||||
text: 'Text',
|
||||
styles: {
|
||||
logo_slogan: 'Logo + slogan',
|
||||
logo: 'Logo',
|
||||
},
|
||||
logo_image_url: 'Logo image URL',
|
||||
slogan: 'Slogan',
|
||||
slogan_placeholder: 'Unleash your creativity',
|
||||
|
|
Loading…
Reference in a new issue