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:
parent
77e1033751
commit
5b3612c153
12 changed files with 173 additions and 125 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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, let’s 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 {
|
||||
|
|
|
@ -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, let’s 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
|
||||
|
||||
|
|
|
@ -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, let’s 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
|
||||
|
||||
|
|
|
@ -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, let’s 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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
135
packages/console/src/mdx-components/UriInputField/index.tsx
Normal file
135
packages/console/src/mdx-components/UriInputField/index.tsx
Normal 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;
|
|
@ -71,6 +71,7 @@ const Guide = ({ app, isCompact, onClose }: Props) => {
|
|||
<GuideComponent
|
||||
appId={appId}
|
||||
activeStepIndex={activeStepIndex}
|
||||
isCompact={isCompact}
|
||||
onNext={(nextIndex: number) => {
|
||||
setActiveStepIndex(nextIndex);
|
||||
}}
|
||||
|
|
Loading…
Reference in a new issue