0
Fork 0
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:
Wang Sijie 2022-04-14 14:53:21 +08:00 committed by GitHub
commit a71e0dfdb6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 201 additions and 24 deletions

View file

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

View file

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

View file

@ -0,0 +1,7 @@
@use '@/scss/underscore' as _;
.title {
color: var(--color-neutral-variant-60);
font: var(--font-subhead-cap);
margin-top: _.unit(8);
}

View file

@ -7,6 +7,8 @@
.setup {
flex: 1;
margin-right: _.unit(3);
height: 100%;
overflow-y: auto;
.tabs {
padding-top: _.unit(2);

View file

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

View file

@ -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 wont have dark mode if its 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',

View file

@ -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 wont have dark mode if its 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',