mirror of
https://github.com/logto-io/logto.git
synced 2025-01-06 20:40:08 -05:00
Merge pull request #791 from logto-io/charles-log-2403-store-get-started-progress-in-settings-config
feat(console): support persisting get-started progress in settings config
This commit is contained in:
commit
1255c6d1e7
11 changed files with 104 additions and 21 deletions
30
packages/console/src/hooks/use-configs.ts
Normal file
30
packages/console/src/hooks/use-configs.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
import { AdminConsoleConfig, Setting } from '@logto/schemas';
|
||||
import useSWR from 'swr';
|
||||
|
||||
import useApi, { RequestError } from './use-api';
|
||||
|
||||
const useAdminConsoleConfigs = () => {
|
||||
const { data: settings, error, mutate } = useSWR<Setting, RequestError>('/api/settings');
|
||||
const api = useApi();
|
||||
|
||||
const updateConfigs = async (delta: Partial<AdminConsoleConfig>) => {
|
||||
const updatedSettings = await api
|
||||
.patch('/api/settings', {
|
||||
json: {
|
||||
adminConsole: {
|
||||
...delta,
|
||||
},
|
||||
},
|
||||
})
|
||||
.json<Setting>();
|
||||
void mutate(updatedSettings);
|
||||
};
|
||||
|
||||
return {
|
||||
configs: settings?.adminConsole,
|
||||
error,
|
||||
updateConfigs,
|
||||
};
|
||||
};
|
||||
|
||||
export default useAdminConsoleConfigs;
|
|
@ -9,6 +9,7 @@ import ModalLayout from '@/components/ModalLayout';
|
|||
import RadioGroup, { Radio } from '@/components/RadioGroup';
|
||||
import TextInput from '@/components/TextInput';
|
||||
import useApi from '@/hooks/use-api';
|
||||
import useAdminConsoleConfigs from '@/hooks/use-configs';
|
||||
import { applicationTypeI18nKey } from '@/types/applications';
|
||||
import { GuideForm } from '@/types/guide';
|
||||
|
||||
|
@ -27,6 +28,7 @@ type Props = {
|
|||
};
|
||||
|
||||
const CreateForm = ({ onClose }: Props) => {
|
||||
const { updateConfigs } = useAdminConsoleConfigs();
|
||||
const [createdApp, setCreatedApp] = useState<Application>();
|
||||
const [isGetStartedModalOpen, setIsGetStartedModalOpen] = useState(false);
|
||||
const {
|
||||
|
@ -72,6 +74,7 @@ const CreateForm = ({ onClose }: Props) => {
|
|||
},
|
||||
})
|
||||
.json<Application>();
|
||||
await updateConfigs({ createApplication: true });
|
||||
setCreatedApp(application);
|
||||
closeModal();
|
||||
};
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { ConnectorDTO, ConnectorType } from '@logto/schemas';
|
||||
import { conditional } from '@silverhand/essentials';
|
||||
import i18next from 'i18next';
|
||||
import React, { useState } from 'react';
|
||||
import { Controller, FormProvider, useForm } from 'react-hook-form';
|
||||
|
@ -14,6 +15,7 @@ import DangerousRaw from '@/components/DangerousRaw';
|
|||
import IconButton from '@/components/IconButton';
|
||||
import Spacer from '@/components/Spacer';
|
||||
import useApi from '@/hooks/use-api';
|
||||
import useAdminConsoleConfigs from '@/hooks/use-configs';
|
||||
import Close from '@/icons/Close';
|
||||
import Step from '@/mdx-components/Step';
|
||||
import SenderTester from '@/pages/ConnectorDetails/components/SenderTester';
|
||||
|
@ -31,6 +33,7 @@ type Props = {
|
|||
|
||||
const GuideModal = ({ connector, isOpen, onClose }: Props) => {
|
||||
const api = useApi();
|
||||
const { updateConfigs } = useAdminConsoleConfigs();
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
const {
|
||||
id: connectorId,
|
||||
|
@ -63,6 +66,10 @@ const GuideModal = ({ connector, isOpen, onClose }: Props) => {
|
|||
})
|
||||
.json<ConnectorDTO>();
|
||||
|
||||
await updateConfigs({
|
||||
...conditional(!isSocialConnector && { configurePasswordless: true }),
|
||||
...conditional(isSocialConnector && { configureSocialSignIn: true }),
|
||||
});
|
||||
setActiveStepIndex(activeStepIndex + 1);
|
||||
toast.success(t('connector_details.save_success'));
|
||||
} catch (error: unknown) {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { AdminConsoleKey, I18nKey } from '@logto/phrases';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import cakeIcon from '@/assets/images/cake.svg';
|
||||
import crabIcon from '@/assets/images/crab.svg';
|
||||
|
@ -11,11 +12,15 @@ import owlIcon from '@/assets/images/owl.svg';
|
|||
import Button from '@/components/Button';
|
||||
import Card from '@/components/Card';
|
||||
import Spacer from '@/components/Spacer';
|
||||
import useAdminConsoleConfigs from '@/hooks/use-configs';
|
||||
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
const GetStarted = () => {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
const { updateConfigs } = useAdminConsoleConfigs();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const data: Array<{
|
||||
title: AdminConsoleKey;
|
||||
subtitle: AdminConsoleKey;
|
||||
|
@ -28,8 +33,9 @@ const GetStarted = () => {
|
|||
subtitle: 'get_started.card1_subtitle',
|
||||
icon: grinningFaceIcon,
|
||||
buttonText: 'general.check_out',
|
||||
onClick: () => {
|
||||
console.log('tada!');
|
||||
onClick: async () => {
|
||||
void updateConfigs({ checkDemo: true });
|
||||
window.open('https://fake.demo.com', '_blank');
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -38,7 +44,7 @@ const GetStarted = () => {
|
|||
icon: cakeIcon,
|
||||
buttonText: 'general.create',
|
||||
onClick: () => {
|
||||
console.log('tada!');
|
||||
navigate('/applications');
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -47,7 +53,7 @@ const GetStarted = () => {
|
|||
icon: drinkIcon,
|
||||
buttonText: 'general.create',
|
||||
onClick: () => {
|
||||
console.log('tada!');
|
||||
navigate('/connectors');
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -56,7 +62,7 @@ const GetStarted = () => {
|
|||
icon: crabIcon,
|
||||
buttonText: 'general.set_up',
|
||||
onClick: () => {
|
||||
console.log('tada!');
|
||||
navigate('/connectors/social');
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -65,7 +71,7 @@ const GetStarted = () => {
|
|||
icon: owlIcon,
|
||||
buttonText: 'general.customize',
|
||||
onClick: () => {
|
||||
console.log('tada!');
|
||||
navigate('/sign-in-experience');
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -74,7 +80,8 @@ const GetStarted = () => {
|
|||
icon: frogIcon,
|
||||
buttonText: 'general.check_out',
|
||||
onClick: () => {
|
||||
console.log('tada!');
|
||||
void updateConfigs({ checkFurtherReadings: true });
|
||||
window.open('https://further.readings.com', '_blank');
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
|
@ -8,7 +8,11 @@ import CardTitle from '@/components/CardTitle';
|
|||
|
||||
import * as styles from './Welcome.module.scss';
|
||||
|
||||
const Welcome = () => {
|
||||
type Props = {
|
||||
onStart: () => void;
|
||||
};
|
||||
|
||||
const Welcome = ({ onStart }: Props) => {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
|
||||
return (
|
||||
|
@ -17,7 +21,11 @@ const Welcome = () => {
|
|||
<div className={styles.content}>
|
||||
<img src={WelcomeImage} />
|
||||
<div>{t('sign_in_exp.welcome.title')}</div>
|
||||
<Button title="admin_console.sign_in_exp.welcome.get_started" type="primary" />
|
||||
<Button
|
||||
title="admin_console.sign_in_exp.welcome.get_started"
|
||||
type="primary"
|
||||
onClick={onStart}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Setting, SignInExperience as SignInExperienceType } from '@logto/schemas';
|
||||
import { SignInExperience as SignInExperienceType } from '@logto/schemas';
|
||||
import classNames from 'classnames';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
|
@ -13,6 +13,7 @@ import Card from '@/components/Card';
|
|||
import CardTitle from '@/components/CardTitle';
|
||||
import TabNav, { TabNavLink } from '@/components/TabNav';
|
||||
import useApi, { RequestError } from '@/hooks/use-api';
|
||||
import useAdminConsoleConfigs from '@/hooks/use-configs';
|
||||
import * as detailsStyles from '@/scss/details.module.scss';
|
||||
import * as modalStyles from '@/scss/modal.module.scss';
|
||||
|
||||
|
@ -30,8 +31,10 @@ const SignInExperience = () => {
|
|||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
const { tab } = useParams();
|
||||
const { data, error, mutate } = useSWR<SignInExperienceType, RequestError>('/api/sign-in-exp');
|
||||
const { data: settings, error: settingsError } = useSWR<Setting, RequestError>('/api/settings');
|
||||
const { configs, error: configError, updateConfigs } = useAdminConsoleConfigs();
|
||||
const [dataToCompare, setDataToCompare] = useState<SignInExperienceType>();
|
||||
const [showWelcome, setShowWelcome] = useState(!configs?.customizeSignInExperience);
|
||||
|
||||
const methods = useForm<SignInExperienceForm>();
|
||||
const {
|
||||
reset,
|
||||
|
@ -54,6 +57,7 @@ const SignInExperience = () => {
|
|||
})
|
||||
.json<SignInExperienceType>();
|
||||
void mutate(updatedData);
|
||||
await updateConfigs({ customizeSignInExperience: true });
|
||||
toast.success(t('application_details.save_success'));
|
||||
};
|
||||
|
||||
|
@ -74,16 +78,22 @@ const SignInExperience = () => {
|
|||
await saveData();
|
||||
});
|
||||
|
||||
if (!settings && !settingsError) {
|
||||
if (!configs && !configError) {
|
||||
return <div>loading</div>;
|
||||
}
|
||||
|
||||
if (settingsError) {
|
||||
return <div>{settingsError.body.message}</div>;
|
||||
if (configError) {
|
||||
return <div>{configError.body.message}</div>;
|
||||
}
|
||||
|
||||
if (!settings?.adminConsole.experienceGuideDone) {
|
||||
return <Welcome />;
|
||||
if (showWelcome) {
|
||||
return (
|
||||
<Welcome
|
||||
onStart={() => {
|
||||
setShowWelcome(false);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -48,6 +48,12 @@ export const mockSetting: Setting = {
|
|||
adminConsole: {
|
||||
language: Language.English,
|
||||
appearanceMode: AppearanceMode.SyncWithSystem,
|
||||
checkDemo: false,
|
||||
createApplication: false,
|
||||
configurePasswordless: false,
|
||||
configureSocialSignIn: false,
|
||||
customizeSignInExperience: false,
|
||||
checkFurtherReadings: false,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -16,11 +16,11 @@ export default function settingRoutes<T extends AuthedRouter>(router: T) {
|
|||
router.patch(
|
||||
'/settings',
|
||||
koaGuard({
|
||||
body: Settings.createGuard.omit({ id: true }).partial(),
|
||||
body: Settings.createGuard.omit({ id: true }).deepPartial(),
|
||||
}),
|
||||
async (ctx, next) => {
|
||||
const { body: setting } = ctx.guard;
|
||||
const { id, ...rest } = await updateSetting(setting);
|
||||
const { body: partialSettings } = ctx.guard;
|
||||
const { id, ...rest } = await updateSetting(partialSettings);
|
||||
ctx.body = rest;
|
||||
|
||||
return next();
|
||||
|
|
|
@ -283,7 +283,7 @@ const translation = {
|
|||
card3_title: '轻松配置连接器,实现无密码登录',
|
||||
card3_subtitle:
|
||||
'Setup a mobile, single page or traditional application to use Logto for Authentication.',
|
||||
card4_title: 'One click to sign in',
|
||||
card4_title: '轻松连接社会化平台,一键登录',
|
||||
card4_subtitle:
|
||||
'With this step, users can use email or phone numbers to sign in without a password or use social identity to sign in.',
|
||||
card5_title: '自定义您的 sign-in experience',
|
||||
|
|
|
@ -143,8 +143,14 @@ export enum AppearanceMode {
|
|||
export const adminConsoleConfigGuard = z.object({
|
||||
language: z.nativeEnum(Language),
|
||||
appearanceMode: z.nativeEnum(AppearanceMode),
|
||||
experienceGuideDone: z.boolean().optional(),
|
||||
experienceNoticeConfirmed: z.boolean().optional(),
|
||||
// Get started challenges
|
||||
checkDemo: z.boolean(),
|
||||
createApplication: z.boolean(),
|
||||
configurePasswordless: z.boolean(),
|
||||
configureSocialSignIn: z.boolean(),
|
||||
customizeSignInExperience: z.boolean(),
|
||||
checkFurtherReadings: z.boolean(),
|
||||
});
|
||||
|
||||
export type AdminConsoleConfig = z.infer<typeof adminConsoleConfigGuard>;
|
||||
|
|
|
@ -11,5 +11,11 @@ export const createDefaultSetting = (): Readonly<CreateSetting> =>
|
|||
adminConsole: {
|
||||
language: Language.English,
|
||||
appearanceMode: AppearanceMode.SyncWithSystem,
|
||||
checkDemo: false,
|
||||
createApplication: false,
|
||||
configurePasswordless: false,
|
||||
configureSocialSignIn: false,
|
||||
customizeSignInExperience: false,
|
||||
checkFurtherReadings: false,
|
||||
},
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue