mirror of
https://github.com/logto-io/logto.git
synced 2025-02-24 22:05:56 -05:00
Merge pull request #941 from logto-io/charles-log-2588-add-integration-guide-for-native-sdks
refactor(console): refactor integration guide to support native SDKs
This commit is contained in:
commit
95e394ba1c
13 changed files with 521 additions and 110 deletions
|
@ -1,3 +1,4 @@
|
||||||
|
import MultiTextInputField from '@mdx/components/MultiTextInputField';
|
||||||
import Step from '@mdx/components/Step';
|
import Step from '@mdx/components/Step';
|
||||||
import Tabs from '@mdx/components/Tabs';
|
import Tabs from '@mdx/components/Tabs';
|
||||||
import TabItem from '@mdx/components/TabItem';
|
import TabItem from '@mdx/components/TabItem';
|
||||||
|
@ -12,8 +13,16 @@ import TabItem from '@mdx/components/TabItem';
|
||||||
>
|
>
|
||||||
|
|
||||||
### Prerequisite
|
### Prerequisite
|
||||||
* Minimal Android SDK: Level 24 (TBD)
|
* Minimal Android SDK: Level 24
|
||||||
|
|
||||||
|
Add the `mavenCentral()` repository to your Gradle project build file:
|
||||||
|
```kotlin
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Add the Logto Android SDK to your dependencies:
|
||||||
<Tabs>
|
<Tabs>
|
||||||
|
|
||||||
<TabItem value="kotlin" label="Kotlin">
|
<TabItem value="kotlin" label="Kotlin">
|
||||||
|
@ -55,7 +64,7 @@ The `Redirect URI` indicates the endpoint that the application used to receive a
|
||||||
|
|
||||||
### Configure Redirect URI in Admin Console
|
### Configure Redirect URI in Admin Console
|
||||||
|
|
||||||
Add the following value to Redirect URIs on the application settings page.
|
Add the following value to Redirect URIs input field
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$(LOGTO_REDIRECT_SCHEME)://$(YOUR_APP_PACKAGE)/callback
|
$(LOGTO_REDIRECT_SCHEME)://$(YOUR_APP_PACKAGE)/callback
|
||||||
|
@ -67,6 +76,8 @@ Notes:
|
||||||
|
|
||||||
e.g. `io.logto.android://io.logto.sample/callback`
|
e.g. `io.logto.android://io.logto.sample/callback`
|
||||||
|
|
||||||
|
<MultiTextInputField name="redirectUris" title="Redirect URI" onError={() => props.onError(2)} />
|
||||||
|
|
||||||
### Configure Logto Android SDK
|
### Configure Logto Android SDK
|
||||||
|
|
||||||
<Tabs>
|
<Tabs>
|
||||||
|
@ -132,7 +143,7 @@ Notes:
|
||||||
|
|
||||||
<Step
|
<Step
|
||||||
title="Sign In"
|
title="Sign In"
|
||||||
subtitle="Sin In to your application by Logto and do some extra works"
|
subtitle="Sign In to your application by Logto and do some extra works"
|
||||||
index={2}
|
index={2}
|
||||||
activeIndex={props.activeStepIndex}
|
activeIndex={props.activeStepIndex}
|
||||||
invalidIndex={props.invalidStepIndex}
|
invalidIndex={props.invalidStepIndex}
|
||||||
|
|
|
@ -0,0 +1,284 @@
|
||||||
|
import MultiTextInputField from '@mdx/components/MultiTextInputField';
|
||||||
|
import Step from '@mdx/components/Step';
|
||||||
|
import Tabs from '@mdx/components/Tabs';
|
||||||
|
import TabItem from '@mdx/components/TabItem';
|
||||||
|
|
||||||
|
<Step
|
||||||
|
title="安装 SDK"
|
||||||
|
subtitle="从 Gradle 安装 Logto Android SDK"
|
||||||
|
index={0}
|
||||||
|
activeIndex={props.activeStepIndex}
|
||||||
|
invalidIndex={props.invalidStepIndex}
|
||||||
|
onNext={() => props.onNext(1)}
|
||||||
|
>
|
||||||
|
|
||||||
|
### 前提条件
|
||||||
|
* 最小 Android SDK 版本: Level 24
|
||||||
|
|
||||||
|
将 `mavenCentral()` 添加到构建脚本中:
|
||||||
|
```kotlin
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
添加 Logto Android SDK 依赖:
|
||||||
|
<Tabs>
|
||||||
|
|
||||||
|
<TabItem value="kotlin" label="Kotlin">
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
dependencies {
|
||||||
|
implementation("io.logto.sdk:android:1.0.0")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
|
||||||
|
<TabItem value="groovy" label="Groovy">
|
||||||
|
|
||||||
|
```groovy
|
||||||
|
dependencies {
|
||||||
|
implementation 'io.logto.sdk:android:1.0.0'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
</Step>
|
||||||
|
|
||||||
|
<Step
|
||||||
|
title="配置"
|
||||||
|
subtitle="Configure your application and LogtoClient"
|
||||||
|
index={1}
|
||||||
|
activeIndex={props.activeStepIndex}
|
||||||
|
invalidIndex={props.invalidStepIndex}
|
||||||
|
onNext={() => props.onNext(2)}
|
||||||
|
>
|
||||||
|
|
||||||
|
### 配置 Redirect URI
|
||||||
|
|
||||||
|
Redirect URI 指定了应用用来接受授权结果的端口,Logto Android SDK 内部实现了该重定向的功能
|
||||||
|
|
||||||
|
### 在管理员控制台中配置 Redirect URI
|
||||||
|
|
||||||
|
将以下值添加到下面的 Redirect URI 输入框中:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$(LOGTO_REDIRECT_SCHEME)://$(YOUR_APP_PACKAGE)/callback
|
||||||
|
```
|
||||||
|
|
||||||
|
注意:
|
||||||
|
- `LOGTO_REDIRECT_SCHEME` 应为自定义的反向域名格式的一串字符。
|
||||||
|
- 将上述的 `$(LOGTO_REDIRECT_SCHEME)` 替换成你定义的值。
|
||||||
|
|
||||||
|
例: `io.logto.android://io.logto.sample/callback`
|
||||||
|
|
||||||
|
<MultiTextInputField name="redirectUris" title="Redirect URI" onError={() => props.onError(1)} />
|
||||||
|
|
||||||
|
### 配置 Logto Android SDK
|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
|
||||||
|
<TabItem value="kotlin" label="Kotlin">
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
import io.logto.sdk.android.LogtoClient
|
||||||
|
import io.logto.sdk.android.type.LogtoConfig
|
||||||
|
|
||||||
|
class MainActivity : AppCompatActivity() {
|
||||||
|
val logtoConfig = LogtoConfig(
|
||||||
|
endpoint = "<your-logto-endpoint>",
|
||||||
|
appId = "<your-app-id>",
|
||||||
|
scopes = null,
|
||||||
|
resources = null,
|
||||||
|
usingPersistStorage = true,
|
||||||
|
)
|
||||||
|
|
||||||
|
val logtoClient = LogtoClient(logtoConfig, application)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
|
||||||
|
<TabItem value="java" label="Java">
|
||||||
|
|
||||||
|
```java
|
||||||
|
import io.logto.sdk.android.LogtoClient;
|
||||||
|
import io.logto.sdk.android.type.LogtoConfig;
|
||||||
|
|
||||||
|
public class MainActivity extends AppCompatActivity {
|
||||||
|
private LogtoClient logtoClient;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_main);
|
||||||
|
|
||||||
|
LogtoConfig logtoConfig = new LogtoConfig(
|
||||||
|
"<your-logto-endpoint>",
|
||||||
|
"<your-app-id>",
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
logtoClient = new LogtoClient(logtoConfig, getApplication());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
- `<your-logto-endpoint>` 是你运行 Logto 服务所在的地址. 若你的 Logto 服务运行在 `http://localhost:300`,则 `<your-logto-endpoint` 为 `http://localhost:300`
|
||||||
|
- `<your-app-id>` 是你为自己的应用在管理员控制台中所创建的客户端ID.
|
||||||
|
|
||||||
|
</Step>
|
||||||
|
|
||||||
|
<Step
|
||||||
|
title="登录"
|
||||||
|
subtitle="Sign In to your application by Logto and do some extra works"
|
||||||
|
index={2}
|
||||||
|
activeIndex={props.activeStepIndex}
|
||||||
|
invalidIndex={props.invalidStepIndex}
|
||||||
|
onNext={() => props.onNext(3)}
|
||||||
|
>
|
||||||
|
|
||||||
|
### 执行登录
|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
|
||||||
|
<TabItem value="kotlin" label="Kotlin">
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
logtoClient.signInWithBrowser(
|
||||||
|
this,
|
||||||
|
"io.logto.android://io.logto.sample/callback",
|
||||||
|
) { logtoException: LogtoException? ->
|
||||||
|
// 后续处理逻辑
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
|
||||||
|
<TabItem value="java" label="Java">
|
||||||
|
|
||||||
|
```java
|
||||||
|
logtoClient.signInWithBrowser(
|
||||||
|
this,
|
||||||
|
"io.logto.android://io.logto.sample/callback",
|
||||||
|
logtoException -> {
|
||||||
|
// 后续处理逻辑
|
||||||
|
}
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
### 登录成功后,你可以使用 SDK 提供的一些 API 来实现自己的业务逻辑
|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
|
||||||
|
<TabItem value="kotlin" label="Kotlin">
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
logtoClient.getAccessToken { logtoException: LogtoException?, result: AccessToken? ->
|
||||||
|
// 后续处理逻辑
|
||||||
|
}
|
||||||
|
|
||||||
|
logtoClient.getIdTokenClaims { logtoException: LogtoException?, result: IdTokenClaims? ->
|
||||||
|
// 后续处理逻辑
|
||||||
|
}
|
||||||
|
|
||||||
|
logtoClient.fetchUserInfo { logtoException: LogtoException?, userInfoResponse: UserInfoResponse? ->
|
||||||
|
// 后续处理逻辑
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
|
||||||
|
<TabItem value="java" label="Java">
|
||||||
|
|
||||||
|
```java
|
||||||
|
logtoClient.getAccessToken((logtoException, accessToken) -> {
|
||||||
|
// 后续处理逻辑
|
||||||
|
});
|
||||||
|
|
||||||
|
logtoClient.getIdTokenClaims((logtoException, idTokenClaims) -> {
|
||||||
|
// 后续处理逻辑
|
||||||
|
});
|
||||||
|
|
||||||
|
logtoClient.fetchUserInfo((logtoException, userInfoResponse) -> {
|
||||||
|
// 后续处理逻辑
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
</Step>
|
||||||
|
|
||||||
|
<Step
|
||||||
|
title="登出"
|
||||||
|
index={3}
|
||||||
|
activeIndex={props.activeStepIndex}
|
||||||
|
invalidIndex={props.invalidStepIndex}
|
||||||
|
onNext={() => props.onNext(4)}
|
||||||
|
>
|
||||||
|
|
||||||
|
### 执行登出
|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
|
||||||
|
<TabItem value="kotlin" label="Kotlin">
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
logtoClient.signOut { logtoException: LogtoException? ->
|
||||||
|
// 后续处理逻辑
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
|
||||||
|
<TabItem value="java" label="Java">
|
||||||
|
|
||||||
|
```java
|
||||||
|
logtoClient.signOut(logtoException -> {
|
||||||
|
// 后续处理逻辑
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
注意:
|
||||||
|
|
||||||
|
- 登出操作会清除本地存储的用户相关凭据,即使在登出过程中发生了异常。
|
||||||
|
|
||||||
|
</Step>
|
||||||
|
|
||||||
|
<Step
|
||||||
|
title="延伸阅读"
|
||||||
|
subtitle="3 steps"
|
||||||
|
index={4}
|
||||||
|
activeIndex={props.activeStepIndex}
|
||||||
|
invalidIndex={props.invalidStepIndex}
|
||||||
|
buttonText="general.done"
|
||||||
|
buttonHtmlType="submit"
|
||||||
|
>
|
||||||
|
|
||||||
|
- 获取用户信息
|
||||||
|
- 配置社会化登录
|
||||||
|
- 访问受保护的 API 资源
|
||||||
|
|
||||||
|
</Step>
|
118
packages/console/src/assets/docs/tutorial/integrate-sdk/ios.mdx
Normal file
118
packages/console/src/assets/docs/tutorial/integrate-sdk/ios.mdx
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
import MultiTextInputField from '@mdx/components/MultiTextInputField';
|
||||||
|
import Step from '@mdx/components/Step';
|
||||||
|
import Tabs from '@mdx/components/Tabs';
|
||||||
|
import TabItem from '@mdx/components/TabItem';
|
||||||
|
|
||||||
|
<Step
|
||||||
|
title="Install SDK"
|
||||||
|
subtitle="Add Logto SDK as a Dependency"
|
||||||
|
index={0}
|
||||||
|
activeIndex={props.activeStepIndex}
|
||||||
|
invalidIndex={props.invalidStepIndex}
|
||||||
|
onNext={() => props.onNext(1)}
|
||||||
|
>
|
||||||
|
|
||||||
|
Use the following URL to add Logto SDK as a dependency in Swift Package Manager.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
https://github.com/logto-io/swift.git
|
||||||
|
```
|
||||||
|
|
||||||
|
Since Xcode 11, you can [directly import a swift package](https://developer.apple.com/documentation/swift_packages/adding_package_dependencies_to_your_app) w/o any additional tool.
|
||||||
|
|
||||||
|
We do not support **Carthage** and **CocoaPods** at the time due to some technical issues.
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Carthage</summary>
|
||||||
|
Carthage <a href="https://github.com/Carthage/Carthage/issues/1226#issuecomment-290931385">needs a `xcodeproj` file to build</a>, but `swift package generate-xcodeproj` will report a failure since we are using binary targets for native social plugins. We will try to find a workaround later.
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>CocoaPods</summary>
|
||||||
|
CocoaPods <a href="https://github.com/CocoaPods/CocoaPods/issues/3276">does not support local dependency</a> and monorepo, thus it's hard to create a `.podspec` for this repo.
|
||||||
|
</details>
|
||||||
|
|
||||||
|
</Step>
|
||||||
|
<Step
|
||||||
|
title="Init LogtoClient"
|
||||||
|
index={1}
|
||||||
|
activeIndex={props.activeStepIndex}
|
||||||
|
invalidIndex={props.invalidStepIndex}
|
||||||
|
onNext={() => props.onNext(2)}
|
||||||
|
>
|
||||||
|
|
||||||
|
```swift
|
||||||
|
import Logto
|
||||||
|
|
||||||
|
let config = try? LogtoConfig(
|
||||||
|
endpoint: "<your-logto-endpoint>",
|
||||||
|
appId: "<your-application-id>"
|
||||||
|
)
|
||||||
|
let logtoClient = LogtoClient(useConfig: config)
|
||||||
|
```
|
||||||
|
|
||||||
|
By default, we store credentials like ID Token and Refresh Token in Keychain. Thus the user doesn't need to sign in again when he returns.
|
||||||
|
|
||||||
|
To turn off this behavior, set `usingPersistStorage` to `false`:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
let config = try? LogtoConfig(
|
||||||
|
// ...
|
||||||
|
usingPersistStorage: false
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
</Step>
|
||||||
|
<Step
|
||||||
|
title="Sign In"
|
||||||
|
index={2}
|
||||||
|
activeIndex={props.activeStepIndex}
|
||||||
|
invalidIndex={props.invalidStepIndex}
|
||||||
|
onNext={() => props.onNext(3)}
|
||||||
|
>
|
||||||
|
|
||||||
|
First, let’s configure your redirect URI
|
||||||
|
|
||||||
|
<MultiTextInputField name="redirectUris" title="Redirect URI" onError={() => props.onError(2)} />
|
||||||
|
|
||||||
|
```swift
|
||||||
|
do {
|
||||||
|
try await client.signInWithBrowser(redirectUri: "<your-redirect-uri>")
|
||||||
|
print(client.isAuthenticated) // true
|
||||||
|
} catch let error as LogtoClientErrors.SignIn {
|
||||||
|
// error occured during sign in
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
</Step>
|
||||||
|
<Step
|
||||||
|
title="Sign Out"
|
||||||
|
subtitle="1 step"
|
||||||
|
index={3}
|
||||||
|
activeIndex={props.activeStepIndex}
|
||||||
|
invalidIndex={props.invalidStepIndex}
|
||||||
|
onNext={() => props.onNext(4)}
|
||||||
|
>
|
||||||
|
|
||||||
|
Calling `.signOut()` will clean all the Logto data in Keychain, if it has.
|
||||||
|
|
||||||
|
```swift
|
||||||
|
await client.signOut()
|
||||||
|
```
|
||||||
|
|
||||||
|
</Step>
|
||||||
|
<Step
|
||||||
|
title="Further Readings"
|
||||||
|
subtitle="3 steps"
|
||||||
|
index={4}
|
||||||
|
activeIndex={props.activeStepIndex}
|
||||||
|
invalidIndex={props.invalidStepIndex}
|
||||||
|
buttonText="general.done"
|
||||||
|
buttonHtmlType="submit"
|
||||||
|
>
|
||||||
|
|
||||||
|
- [SDK Documentation](https://link-url-here.org)
|
||||||
|
- [OIDC Documentation](https://link-url-here.org)
|
||||||
|
- [Calling API to fetch accessToken](https://link-url-here.org)
|
||||||
|
|
||||||
|
</Step>
|
|
@ -43,7 +43,6 @@ pnpm add @logto/vue
|
||||||
</TabItem>
|
</TabItem>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Step>
|
</Step>
|
||||||
|
|
||||||
<Step
|
<Step
|
||||||
title="Initiate LogtoClient"
|
title="Initiate LogtoClient"
|
||||||
subtitle="1 step"
|
subtitle="1 step"
|
||||||
|
@ -70,7 +69,6 @@ app.mount("#app");
|
||||||
```
|
```
|
||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
|
|
||||||
<Step
|
<Step
|
||||||
title="Sign In"
|
title="Sign In"
|
||||||
subtitle="2 steps"
|
subtitle="2 steps"
|
||||||
|
@ -149,7 +147,6 @@ const { isAuthenticated } = useLogto();
|
||||||
```
|
```
|
||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
|
|
||||||
<Step
|
<Step
|
||||||
title="Sign Out"
|
title="Sign Out"
|
||||||
subtitle="1 step"
|
subtitle="1 step"
|
||||||
|
@ -178,7 +175,6 @@ const onClickSignOut = () => signOut('http://localhost:1234');
|
||||||
```
|
```
|
||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
|
|
||||||
<Step
|
<Step
|
||||||
title="Further Readings"
|
title="Further Readings"
|
||||||
subtitle="3 steps"
|
subtitle="3 steps"
|
||||||
|
|
|
@ -43,7 +43,6 @@ pnpm add @logto/vue
|
||||||
</TabItem>
|
</TabItem>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Step>
|
</Step>
|
||||||
|
|
||||||
<Step
|
<Step
|
||||||
title="初始化 LogtoClient"
|
title="初始化 LogtoClient"
|
||||||
subtitle="1 step"
|
subtitle="1 step"
|
||||||
|
@ -70,7 +69,6 @@ app.mount("#app");
|
||||||
```
|
```
|
||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
|
|
||||||
<Step
|
<Step
|
||||||
title="Sign In"
|
title="Sign In"
|
||||||
subtitle="2 steps"
|
subtitle="2 steps"
|
||||||
|
@ -150,7 +148,6 @@ const { isAuthenticated } = useLogto();
|
||||||
```
|
```
|
||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
|
|
||||||
<Step
|
<Step
|
||||||
title="Sign Out"
|
title="Sign Out"
|
||||||
subtitle="1 step"
|
subtitle="1 step"
|
||||||
|
@ -178,7 +175,6 @@ const onClickSignOut = () => signOut('http://localhost:1234');
|
||||||
```
|
```
|
||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
|
|
||||||
<Step
|
<Step
|
||||||
title="延伸阅读"
|
title="延伸阅读"
|
||||||
subtitle="3 steps"
|
subtitle="3 steps"
|
||||||
|
|
|
@ -130,6 +130,7 @@ const CreateForm = ({ onClose }: Props) => {
|
||||||
{createdApp && (
|
{createdApp && (
|
||||||
<GuideModal
|
<GuideModal
|
||||||
appName={createdApp.name}
|
appName={createdApp.name}
|
||||||
|
appType={createdApp.type}
|
||||||
isOpen={isGetStartedModalOpen}
|
isOpen={isGetStartedModalOpen}
|
||||||
onClose={closeModal}
|
onClose={closeModal}
|
||||||
onComplete={onComplete}
|
onComplete={onComplete}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { ApplicationType } from '@logto/schemas';
|
||||||
import { MDXProvider } from '@mdx-js/react';
|
import { MDXProvider } from '@mdx-js/react';
|
||||||
import i18next from 'i18next';
|
import i18next from 'i18next';
|
||||||
import { MDXProps } from 'mdx/types';
|
import { MDXProps } from 'mdx/types';
|
||||||
|
@ -13,23 +14,29 @@ import IconButton from '@/components/IconButton';
|
||||||
import Spacer from '@/components/Spacer';
|
import Spacer from '@/components/Spacer';
|
||||||
import Close from '@/icons/Close';
|
import Close from '@/icons/Close';
|
||||||
import * as modalStyles from '@/scss/modal.module.scss';
|
import * as modalStyles from '@/scss/modal.module.scss';
|
||||||
import { SupportedJavascriptLibraries } from '@/types/applications';
|
import { applicationTypeAndSdkTypeMappings, SupportedSdk } from '@/types/applications';
|
||||||
import { GuideForm } from '@/types/guide';
|
import { GuideForm } from '@/types/guide';
|
||||||
|
|
||||||
import LibrarySelector from '../LibrarySelector';
|
import SdkSelector from '../SdkSelector';
|
||||||
import StepsSkeleton from '../StepsSkeleton';
|
import StepsSkeleton from '../StepsSkeleton';
|
||||||
import * as styles from './index.module.scss';
|
import * as styles from './index.module.scss';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
appName: string;
|
appName: string;
|
||||||
|
appType: ApplicationType;
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onComplete: (data: GuideForm) => Promise<void>;
|
onComplete: (data: GuideForm) => Promise<void>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Guides: Record<string, LazyExoticComponent<(props: MDXProps) => JSX.Element>> = {
|
const Guides: Record<string, LazyExoticComponent<(props: MDXProps) => JSX.Element>> = {
|
||||||
|
ios: lazy(async () => import('@/assets/docs/tutorial/integrate-sdk/ios.mdx')),
|
||||||
|
android: lazy(async () => import('@/assets/docs/tutorial/integrate-sdk/android.mdx')),
|
||||||
react: lazy(async () => import('@/assets/docs/tutorial/integrate-sdk/react.mdx')),
|
react: lazy(async () => import('@/assets/docs/tutorial/integrate-sdk/react.mdx')),
|
||||||
vue: lazy(async () => import('@/assets/docs/tutorial/integrate-sdk/vue.mdx')),
|
vue: lazy(async () => import('@/assets/docs/tutorial/integrate-sdk/vue.mdx')),
|
||||||
|
'android_zh-cn': lazy(
|
||||||
|
async () => import('@/assets/docs/tutorial/integrate-sdk/android_zh-cn.mdx')
|
||||||
|
),
|
||||||
'react_zh-cn': lazy(async () => import('@/assets/docs/tutorial/integrate-sdk/react_zh-cn.mdx')),
|
'react_zh-cn': lazy(async () => import('@/assets/docs/tutorial/integrate-sdk/react_zh-cn.mdx')),
|
||||||
'vue_zh-cn': lazy(async () => import('@/assets/docs/tutorial/integrate-sdk/vue_zh-cn.mdx')),
|
'vue_zh-cn': lazy(async () => import('@/assets/docs/tutorial/integrate-sdk/vue_zh-cn.mdx')),
|
||||||
};
|
};
|
||||||
|
@ -39,16 +46,15 @@ const onClickFetchSampleProject = (projectName: string) => {
|
||||||
window.open(sampleUrl, '_blank');
|
window.open(sampleUrl, '_blank');
|
||||||
};
|
};
|
||||||
|
|
||||||
const GuideModal = ({ appName, isOpen, onClose, onComplete }: Props) => {
|
const GuideModal = ({ appName, appType, isOpen, onClose, onComplete }: Props) => {
|
||||||
const [subtype, setSubtype] = useState<SupportedJavascriptLibraries>(
|
const sdks = applicationTypeAndSdkTypeMappings[appType];
|
||||||
SupportedJavascriptLibraries.React
|
const [selectedSdk, setSelectedSdk] = useState<SupportedSdk>(sdks[0]);
|
||||||
);
|
|
||||||
const [activeStepIndex, setActiveStepIndex] = useState(-1);
|
const [activeStepIndex, setActiveStepIndex] = useState(-1);
|
||||||
const [invalidStepIndex, setInvalidStepIndex] = useState(-1);
|
const [invalidStepIndex, setInvalidStepIndex] = useState(-1);
|
||||||
|
|
||||||
const locale = i18next.language;
|
const locale = i18next.language;
|
||||||
const guideI18nKey = `${subtype}_${locale}`.toLowerCase();
|
const guideI18nKey = `${selectedSdk}_${locale}`.toLowerCase();
|
||||||
const GuideComponent = Guides[guideI18nKey] ?? Guides[subtype];
|
const GuideComponent = Guides[guideI18nKey] ?? Guides[selectedSdk.toLowerCase()];
|
||||||
|
|
||||||
const methods = useForm<GuideForm>({ mode: 'onSubmit', reValidateMode: 'onChange' });
|
const methods = useForm<GuideForm>({ mode: 'onSubmit', reValidateMode: 'onChange' });
|
||||||
const {
|
const {
|
||||||
|
@ -82,16 +88,16 @@ const GuideModal = ({ appName, isOpen, onClose, onComplete }: Props) => {
|
||||||
type="outline"
|
type="outline"
|
||||||
title="admin_console.applications.guide.get_sample_file"
|
title="admin_console.applications.guide.get_sample_file"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onClickFetchSampleProject(subtype);
|
onClickFetchSampleProject(selectedSdk);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.content}>
|
<div className={styles.content}>
|
||||||
<FormProvider {...methods}>
|
<FormProvider {...methods}>
|
||||||
<form onSubmit={onSubmit}>
|
<form onSubmit={onSubmit}>
|
||||||
{cloneElement(<LibrarySelector libraryName={subtype} />, {
|
{cloneElement(<SdkSelector sdks={sdks} selectedSdk={selectedSdk} />, {
|
||||||
className: styles.banner,
|
className: styles.banner,
|
||||||
onChange: setSubtype,
|
onChange: setSelectedSdk,
|
||||||
onToggle: () => {
|
onToggle: () => {
|
||||||
setActiveStepIndex(0);
|
setActiveStepIndex(0);
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,79 +0,0 @@
|
||||||
import classNames from 'classnames';
|
|
||||||
import React, { useState, useMemo } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
import congrats from '@/assets/images/congrats.svg';
|
|
||||||
import tada from '@/assets/images/tada.svg';
|
|
||||||
import Button from '@/components/Button';
|
|
||||||
import Card from '@/components/Card';
|
|
||||||
import RadioGroup, { Radio } from '@/components/RadioGroup';
|
|
||||||
import { SupportedJavascriptLibraries } from '@/types/applications';
|
|
||||||
|
|
||||||
import * as styles from './index.module.scss';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
className?: string;
|
|
||||||
libraryName?: SupportedJavascriptLibraries;
|
|
||||||
onChange?: (value: string) => void;
|
|
||||||
onToggle?: () => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
const LibrarySelector = ({
|
|
||||||
className,
|
|
||||||
libraryName = SupportedJavascriptLibraries.React,
|
|
||||||
onChange,
|
|
||||||
onToggle,
|
|
||||||
}: Props) => {
|
|
||||||
const [isFolded, setIsFolded] = useState(false);
|
|
||||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
|
||||||
|
|
||||||
const librarySelector = useMemo(
|
|
||||||
() => (
|
|
||||||
<Card className={classNames(styles.card, className)}>
|
|
||||||
<img src={congrats} alt="success" />
|
|
||||||
<div>
|
|
||||||
<div className={styles.title}>{t('applications.guide.title')}</div>
|
|
||||||
<div className={styles.subtitle}>{t('applications.guide.subtitle')}</div>
|
|
||||||
</div>
|
|
||||||
<RadioGroup
|
|
||||||
className={styles.radioGroup}
|
|
||||||
name="libraryName"
|
|
||||||
value={libraryName}
|
|
||||||
type="card"
|
|
||||||
onChange={onChange}
|
|
||||||
>
|
|
||||||
{Object.values(SupportedJavascriptLibraries).map((library) => (
|
|
||||||
<Radio key={library} className={styles.radio} value={library}>
|
|
||||||
{library}
|
|
||||||
</Radio>
|
|
||||||
))}
|
|
||||||
</RadioGroup>
|
|
||||||
<div className={styles.buttonWrapper}>
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
title="general.next"
|
|
||||||
onClick={() => {
|
|
||||||
setIsFolded(true);
|
|
||||||
onToggle?.();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
),
|
|
||||||
[className, libraryName, onChange, onToggle, t]
|
|
||||||
);
|
|
||||||
|
|
||||||
const librarySelectorFolded = useMemo(
|
|
||||||
() => (
|
|
||||||
<div className={classNames(styles.card, styles.folded, className)}>
|
|
||||||
<img src={tada} alt="Tada!" />
|
|
||||||
<span>{t('applications.guide.description_by_library', { library: libraryName })}</span>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
[className, libraryName, t]
|
|
||||||
);
|
|
||||||
|
|
||||||
return isFolded ? librarySelectorFolded : librarySelector;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default LibrarySelector;
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import congrats from '@/assets/images/congrats.svg';
|
||||||
|
import tada from '@/assets/images/tada.svg';
|
||||||
|
import Button from '@/components/Button';
|
||||||
|
import Card from '@/components/Card';
|
||||||
|
import RadioGroup, { Radio } from '@/components/RadioGroup';
|
||||||
|
import { SupportedSdk } from '@/types/applications';
|
||||||
|
|
||||||
|
import * as styles from './index.module.scss';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
className?: string;
|
||||||
|
sdks: readonly SupportedSdk[];
|
||||||
|
selectedSdk: SupportedSdk;
|
||||||
|
onChange?: (value: string) => void;
|
||||||
|
onToggle?: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const SdkSelector = ({ className, sdks, selectedSdk, onChange, onToggle }: Props) => {
|
||||||
|
const [isFolded, setIsFolded] = useState(false);
|
||||||
|
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||||
|
|
||||||
|
if (isFolded) {
|
||||||
|
return (
|
||||||
|
<div className={classNames(styles.card, styles.folded, className)}>
|
||||||
|
<img src={tada} alt="Tada!" />
|
||||||
|
<span>{t('applications.guide.description_by_sdk', { sdk: selectedSdk })}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className={classNames(styles.card, className)}>
|
||||||
|
<img src={congrats} alt="success" />
|
||||||
|
<div>
|
||||||
|
<div className={styles.title}>{t('applications.guide.title')}</div>
|
||||||
|
<div className={styles.subtitle}>{t('applications.guide.subtitle')}</div>
|
||||||
|
</div>
|
||||||
|
<RadioGroup
|
||||||
|
className={styles.radioGroup}
|
||||||
|
name="selectedSdk"
|
||||||
|
value={selectedSdk}
|
||||||
|
type="card"
|
||||||
|
onChange={onChange}
|
||||||
|
>
|
||||||
|
{sdks.length > 1 &&
|
||||||
|
sdks.map((sdk) => (
|
||||||
|
<Radio key={sdk} className={styles.radio} value={sdk}>
|
||||||
|
{sdk}
|
||||||
|
</Radio>
|
||||||
|
))}
|
||||||
|
</RadioGroup>
|
||||||
|
<div className={styles.buttonWrapper}>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
title="general.next"
|
||||||
|
onClick={() => {
|
||||||
|
setIsFolded(true);
|
||||||
|
onToggle?.();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SdkSelector;
|
|
@ -6,8 +6,17 @@ export const applicationTypeI18nKey = Object.freeze({
|
||||||
[ApplicationType.Traditional]: 'applications.type.traditional',
|
[ApplicationType.Traditional]: 'applications.type.traditional',
|
||||||
} as const);
|
} as const);
|
||||||
|
|
||||||
export enum SupportedJavascriptLibraries {
|
export enum SupportedSdk {
|
||||||
Angular = 'angular',
|
iOS = 'iOS',
|
||||||
React = 'react',
|
Android = 'Android',
|
||||||
Vue = 'vue',
|
Angular = 'Angular',
|
||||||
|
React = 'React',
|
||||||
|
Vue = 'Vue',
|
||||||
|
Traditional = 'Traditional',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const applicationTypeAndSdkTypeMappings = Object.freeze({
|
||||||
|
[ApplicationType.Native]: [SupportedSdk.iOS, SupportedSdk.Android],
|
||||||
|
[ApplicationType.SPA]: [SupportedSdk.Angular, SupportedSdk.React, SupportedSdk.Vue],
|
||||||
|
[ApplicationType.Traditional]: [SupportedSdk.Traditional],
|
||||||
|
} as const);
|
||||||
|
|
|
@ -169,9 +169,8 @@ const translation = {
|
||||||
'Follow a step by step guide to integrate your application or get a sample configured with your account settings',
|
'Follow a step by step guide to integrate your application or get a sample configured with your account settings',
|
||||||
title: 'Congratulations! The application has been created successfully.',
|
title: 'Congratulations! The application has been created successfully.',
|
||||||
subtitle:
|
subtitle:
|
||||||
'Now follow the steps below to finish your app settings. Please select the JS library to continue.',
|
'Now follow the steps below to finish your app settings. Please select the SDK type to continue.',
|
||||||
description_by_library:
|
description_by_sdk: 'This quickstart demonstrates how to add Logto to {{sdk}} application.',
|
||||||
'This quickstart demonstrates how to add Logto to {{library}} application.',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
application_details: {
|
application_details: {
|
||||||
|
|
|
@ -168,8 +168,8 @@ const translation = {
|
||||||
header_description:
|
header_description:
|
||||||
'参考如下教程,将 Logto 集成到您的应用中。您也可以点击右侧链接,获取我们为您准备好的示范工程。',
|
'参考如下教程,将 Logto 集成到您的应用中。您也可以点击右侧链接,获取我们为您准备好的示范工程。',
|
||||||
title: '恭喜!您的应用已成功创建。',
|
title: '恭喜!您的应用已成功创建。',
|
||||||
subtitle: '请参考以下步骤完成您的应用设置。首先,请选择您要使用的 Javascript 框架:',
|
subtitle: '请参考以下步骤完成您的应用设置。首先,请选择您要使用的 SDK 类型:',
|
||||||
description_by_library: '本教程向您演示如何在 {{library}} 应用中集成 Logto 登录功能',
|
description_by_sdk: '本教程向您演示如何在 {{sdk}} 应用中集成 Logto 登录功能',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
application_details: {
|
application_details: {
|
||||||
|
|
Loading…
Add table
Reference in a new issue