0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-01-06 20:40:08 -05:00

refactor(console): use single text input in sdk guide on app newly created

This commit is contained in:
Charles Zhao 2022-06-27 20:10:27 +08:00
parent 77e1033751
commit 5b3612c153
No known key found for this signature in database
GPG key ID: 4858774754C92DF2
12 changed files with 173 additions and 125 deletions

View file

@ -1,4 +1,4 @@
import MultiTextInputField from '@mdx/components/MultiTextInputField';
import UriInputField from '@mdx/components/UriInputField';
import Step from '@mdx/components/Step';
import Tabs from '@mdx/components/Tabs';
import TabItem from '@mdx/components/TabItem';
@ -74,7 +74,7 @@ Notes:
e.g. `io.logto.android://io.logto.sample/callback`
<MultiTextInputField appId={props.appId} name="redirectUris" title="Redirect URI" />
<UriInputField appId={props.appId} isSingle={!props.isCompact} name="redirectUris" title="Redirect URI" />
### Configure Logto Android SDK

View file

@ -1,4 +1,4 @@
import MultiTextInputField from '@mdx/components/MultiTextInputField';
import UriInputField from '@mdx/components/UriInputField';
import Step from '@mdx/components/Step';
import Tabs from '@mdx/components/Tabs';
import TabItem from '@mdx/components/TabItem';
@ -74,7 +74,7 @@ $(LOGTO_REDIRECT_SCHEME)://$(YOUR_APP_PACKAGE)/callback
例: `io.logto.android://io.logto.sample/callback`
<MultiTextInputField appId={props.appId} name="redirectUris" title="Redirect URI" />
<UriInputField appId={props.appId} isSingle={!props.isCompact} name="redirectUris" title="Redirect URI" />
### 配置 Logto Android SDK

View file

@ -1,4 +1,4 @@
import MultiTextInputField from '@mdx/components/MultiTextInputField';
import UriInputField from '@mdx/components/UriInputField';
import Step from '@mdx/components/Step';
import Tabs from '@mdx/components/Tabs';
import TabItem from '@mdx/components/TabItem';
@ -70,7 +70,7 @@ let config = try? LogtoConfig(
First, lets configure your redirect URI
<MultiTextInputField appId={props.appId} name="redirectUris" title="Redirect URI" />
<UriInputField appId={props.appId} isSingle={!props.isCompact} name="redirectUris" title="Redirect URI" />
```swift
do {

View file

@ -1,4 +1,4 @@
import MultiTextInputField from '@mdx/components/MultiTextInputField';
import UriInputField from '@mdx/components/UriInputField';
import Step from '@mdx/components/Step';
import Tabs from '@mdx/components/Tabs';
import TabItem from '@mdx/components/TabItem';
@ -107,7 +107,7 @@ const App = () => {
The Logto React SDK provides you tools and hooks to quickly implement your own authorization flow. First, lets enter your redirect URI
<MultiTextInputField appId={props.appId} name="redirectUris" title="Redirect URI" />
<UriInputField appId={props.appId} isSingle={!props.isCompact} name="redirectUris" title="Redirect URI" />
Add the following code to your web app
@ -157,7 +157,7 @@ const App = () => {
Execute signOut() methods will redirect users to the Logto sign out page. After a success sign out, all use session data and auth status will be cleared.
<MultiTextInputField appId={props.appId} name="postLogoutRedirectUris" title="Post Sign-out Redirect URI" />
<UriInputField appId={props.appId} isSingle={!props.isCompact} name="postLogoutRedirectUris" title="Post Sign-out Redirect URI" />
Add the following code to your web app

View file

@ -1,4 +1,4 @@
import MultiTextInputField from '@mdx/components/MultiTextInputField';
import UriInputField from '@mdx/components/UriInputField';
import Step from '@mdx/components/Step';
import Tabs from '@mdx/components/Tabs';
import TabItem from '@mdx/components/TabItem';
@ -108,7 +108,7 @@ const App = () => {
The Logto React SDK provides you tools and hooks to quickly implement your own authorization flow. First, lets enter your redirect URI
<MultiTextInputField appId={props.appId} name="redirectUris" title="Redirect URI" />
<UriInputField appId={props.appId} isSingle={!props.isCompact} name="redirectUris" title="Redirect URI" />
Add the following code to your web app
@ -158,7 +158,7 @@ const App = () => {
Execute signOut() methods will redirect users to the Logto sign out page. After a success sign out, all use session data and auth status will be cleared.
<MultiTextInputField appId={props.appId} name="postLogoutRedirectUris" title="Post Sign-out Redirect URI" />
<UriInputField appId={props.appId} isSingle={!props.isCompact} name="postLogoutRedirectUris" title="Post Sign-out Redirect URI" />
Add the following code to your web app

View file

@ -1,4 +1,4 @@
import MultiTextInputField from '@mdx/components/MultiTextInputField';
import UriInputField from '@mdx/components/UriInputField';
import Step from '@mdx/components/Step';
import Tabs from '@mdx/components/Tabs';
import TabItem from '@mdx/components/TabItem';
@ -83,7 +83,7 @@ In order to handle what comes from Logto, the application needs to have a dedica
First, lets enter your redirect URI. E.g. `http://localhost:1234/callback`
<MultiTextInputField appId={props.appId} name="redirectUris" title="Redirect URI" />
<UriInputField appId={props.appId} isSingle={!props.isCompact} name="redirectUris" title="Redirect URI" />
Then let's create a callback component:
@ -157,7 +157,7 @@ Calling `.signOut()` will clear all the Logto data in memory and LocalStorage, i
To make the user come back to your application after signing out,
it's necessary to add `http://localhost:1234` as one of the Post Sign Out URIs and use the URL as the parameter when calling `.signOut()`.
<MultiTextInputField appId={props.appId} name="postLogoutRedirectUris" title="Post Sign-out Redirect URI" />
<UriInputField appId={props.appId} isSingle={!props.isCompact} name="postLogoutRedirectUris" title="Post Sign-out Redirect URI" />
```ts
import { useLogto } from "@logto/vue";

View file

@ -1,4 +1,4 @@
import MultiTextInputField from '@mdx/components/MultiTextInputField';
import UriInputField from '@mdx/components/UriInputField';
import Step from '@mdx/components/Step';
import Tabs from '@mdx/components/Tabs';
import TabItem from '@mdx/components/TabItem';
@ -83,7 +83,7 @@ app.mount("#app");
但首先, 让我们先在下方输入 redirect URI`http://localhost:1234/callback`
<MultiTextInputField appId={props.appId} name="redirectUris" title="Redirect URI" />
<UriInputField appId={props.appId} isSingle={!props.isCompact} name="redirectUris" title="Redirect URI" />
然后,让我们来创建一个 CallbackView 组件:
@ -157,7 +157,7 @@ const { isAuthenticated } = useLogto();
为了确保用户登出后能够跳转回你的应用,我们需要首先在管理界面中将 `http://localhost:1234` 添加到允许登出后跳转的地址列表Post Sign Out URIs中。
<MultiTextInputField appId={props.appId} name="postLogoutRedirectUris" title="Post Sign-out Redirect URI" />
<UriInputField appId={props.appId} isSingle={!props.isCompact} name="postLogoutRedirectUris" title="Post Sign-out Redirect URI" />
```ts
import { useLogto } from "@logto/vue";

View file

@ -10,7 +10,15 @@ type Props = HTMLProps<HTMLInputElement> & {
};
const TextInput = (
{ hasError = false, errorMessage, icon, disabled, className, readOnly, ...rest }: Props,
{
errorMessage,
hasError = Boolean(errorMessage),
icon,
disabled,
className,
readOnly,
...rest
}: Props,
reference: ForwardedRef<HTMLInputElement>
) => {
return (

View file

@ -1,103 +0,0 @@
import { I18nKey } from '@logto/phrases';
import { Application } from '@logto/schemas';
import React, { useRef } from 'react';
import { Controller, FormProvider, useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import useSWR from 'swr';
import Button from '@/components/Button';
import FormField from '@/components/FormField';
import MultiTextInput from '@/components/MultiTextInput';
import { createValidatorForRhf, convertRhfErrorMessage } from '@/components/MultiTextInput/utils';
import useApi, { RequestError } from '@/hooks/use-api';
import { GuideForm } from '@/types/guide';
import { uriValidator } from '@/utilities/validator';
import * as styles from './index.module.scss';
type Props = {
appId: string;
name: 'redirectUris' | 'postLogoutRedirectUris';
title: I18nKey;
};
const MultiTextInputField = ({ appId, name, title }: Props) => {
const methods = useForm<GuideForm>();
const {
control,
getValues,
handleSubmit,
reset,
formState: { isSubmitting },
} = methods;
const { data, mutate } = useSWR<Application, RequestError>(`/api/applications/${appId}`);
const ref = useRef<HTMLDivElement>(null);
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const api = useApi();
const onSubmit = async (value: string[]) => {
const updatedApp = await api
.patch(`/api/applications/${appId}`, {
json: {
oidcClientMetadata: {
[name]: value.filter(Boolean),
},
},
})
.json<Application>();
void mutate(updatedApp);
// Reset form to set 'isDirty' to false
reset(getValues());
};
return (
<FormProvider {...methods}>
<form>
<FormField isRequired className={styles.field} title={title}>
<Controller
name={name}
control={control}
defaultValue={data?.oidcClientMetadata[name]}
rules={{
validate: createValidatorForRhf({
required: t('errors.required_field_missing_plural', { field: title }),
pattern: {
verify: (value) => !value || uriValidator(value),
message: t('errors.invalid_uri_format'),
},
}),
}}
render={({ field: { onChange, value }, fieldState: { error, isDirty } }) => (
<div ref={ref} className={styles.wrapper}>
<MultiTextInput
title={title}
value={value}
error={convertRhfErrorMessage(error?.message)}
onChange={onChange}
onKeyPress={(event) => {
if (event.key === 'Enter') {
event.preventDefault();
void handleSubmit(async () => onSubmit(value))();
}
}}
/>
<Button
disabled={!isDirty}
isLoading={isSubmitting}
title="general.save"
type="primary"
onClick={handleSubmit(async () => onSubmit(value))}
/>
</div>
)}
/>
</FormField>
</form>
</FormProvider>
);
};
export default MultiTextInputField;

View file

@ -1,10 +1,17 @@
@use '@/scss/underscore' as _;
.wrapper {
display: flex;
align-items: flex-start;
position: relative;
}
.field {
width: 556px;
}
.wrapper {
display: flex;
align-items: flex-start;
.saveButton {
position: absolute;
left: calc(556px + _.unit(3));
top: 0;
}

View file

@ -0,0 +1,135 @@
import { I18nKey } from '@logto/phrases';
import { Application } from '@logto/schemas';
import React, { useRef, KeyboardEvent } from 'react';
import { Controller, FormProvider, useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import useSWR from 'swr';
import Button from '@/components/Button';
import FormField from '@/components/FormField';
import MultiTextInput from '@/components/MultiTextInput';
import { convertRhfErrorMessage, createValidatorForRhf } from '@/components/MultiTextInput/utils';
import TextInput from '@/components/TextInput';
import useApi, { RequestError } from '@/hooks/use-api';
import { GuideForm } from '@/types/guide';
import { uriValidator } from '@/utilities/validator';
import * as styles from './index.module.scss';
type Props = {
appId: string;
name: 'redirectUris' | 'postLogoutRedirectUris';
title: I18nKey;
isSingle?: boolean;
};
const UriInputField = ({ appId, name, title, isSingle = false }: Props) => {
const methods = useForm<Partial<GuideForm>>();
const {
control,
getValues,
handleSubmit,
reset,
formState: { isSubmitting },
} = methods;
const { data, mutate } = useSWR<Application, RequestError>(`/api/applications/${appId}`);
const ref = useRef<HTMLDivElement>(null);
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const api = useApi();
const onSubmit = async (value: string[]) => {
const updatedApp = await api
.patch(`/api/applications/${appId}`, {
json: {
oidcClientMetadata: {
[name]: value.filter(Boolean),
},
},
})
.json<Application>();
void mutate(updatedApp);
// Reset form to set 'isDirty' to false
reset(getValues());
};
const onKeyPress = (event: KeyboardEvent<HTMLInputElement>, value: string[]) => {
if (event.key === 'Enter') {
event.preventDefault();
void handleSubmit(async () => onSubmit(value))();
}
};
return (
<FormProvider {...methods}>
<form>
<FormField isRequired className={styles.field} title={title}>
<Controller
name={name}
control={control}
defaultValue={data?.oidcClientMetadata[name]}
rules={{
validate: createValidatorForRhf({
required: t(
isSingle
? 'errors.required_field_missing'
: 'errors.required_field_missing_plural',
{ field: title }
),
pattern: {
verify: (value) => !value || uriValidator(value),
message: t('errors.invalid_uri_format'),
},
}),
}}
render={({ field: { onChange, value = [] }, fieldState: { error, isDirty } }) => {
const errorObject = convertRhfErrorMessage(error?.message);
return (
<div ref={ref} className={styles.wrapper}>
{isSingle && (
<TextInput
className={styles.field}
title={title}
value={value[0]}
errorMessage={errorObject?.required ?? errorObject?.inputs?.[0]}
onChange={({ currentTarget: { value } }) => {
onChange([value]);
}}
onKeyPress={(event) => {
onKeyPress(event, value);
}}
/>
)}
{!isSingle && (
<MultiTextInput
title={title}
value={value}
error={errorObject}
onChange={onChange}
onKeyPress={(event) => {
onKeyPress(event, value);
}}
/>
)}
<Button
className={styles.saveButton}
disabled={!isDirty}
isLoading={isSubmitting}
title="general.save"
type="primary"
onClick={handleSubmit(async () => onSubmit(value))}
/>
</div>
);
}}
/>
</FormField>
</form>
</FormProvider>
);
};
export default UriInputField;

View file

@ -71,6 +71,7 @@ const Guide = ({ app, isCompact, onClose }: Props) => {
<GuideComponent
appId={appId}
activeStepIndex={activeStepIndex}
isCompact={isCompact}
onNext={(nextIndex: number) => {
setActiveStepIndex(nextIndex);
}}