From 7033bdc2cd918466b5491c2804f53f7819a4021d Mon Sep 17 00:00:00 2001 From: Charles Zhao Date: Tue, 22 Mar 2022 11:15:24 +0800 Subject: [PATCH] feat(core): provide API to fetch get-started markdown metadata --- packages/console/package.json | 8 +- .../public/get-started/react/en/index.json | 3 + .../public/get-started/react/en/step1.md | 34 ++++ .../public/get-started/react/en/step2.md | 35 ++++ .../public/get-started/react/en/step3.md | 47 +++++ .../public/get-started/react/en/step4.md | 26 +++ .../public/get-started/react/en/step5.md | 15 ++ .../public/get-started/react/zh-CN/index.json | 3 + .../public/get-started/react/zh-CN/step1.md | 34 ++++ .../public/get-started/react/zh-CN/step2.md | 35 ++++ .../public/get-started/react/zh-CN/step3.md | 47 +++++ .../public/get-started/react/zh-CN/step4.md | 26 +++ .../public/get-started/react/zh-CN/step5.md | 15 ++ .../components/AppContent/index.module.scss | 1 + .../src/components/Card/index.module.scss | 1 - .../console/src/components/Card/index.tsx | 14 +- .../src/components/CardTitle/index.tsx | 28 +-- packages/console/src/hooks/use-api.ts | 2 +- packages/console/src/hooks/use-swr-fetcher.ts | 2 +- packages/console/src/icons/Arrow.tsx | 31 ++++ packages/console/src/icons/Tick.tsx | 16 ++ packages/console/src/include.d/dom.d.ts | 3 + .../components/GetStarted/index.module.scss | 90 ++++++--- .../components/GetStarted/index.tsx | 171 ++++++++++++------ packages/console/src/scss/_colors.scss | 1 + packages/console/src/utilities/markdown.ts | 39 ++++ packages/phrases/src/locales/en.ts | 11 +- packages/phrases/src/locales/zh-cn.ts | 11 +- pnpm-lock.yaml | 2 + 29 files changed, 623 insertions(+), 128 deletions(-) create mode 100644 packages/console/public/get-started/react/en/index.json create mode 100644 packages/console/public/get-started/react/en/step1.md create mode 100644 packages/console/public/get-started/react/en/step2.md create mode 100644 packages/console/public/get-started/react/en/step3.md create mode 100644 packages/console/public/get-started/react/en/step4.md create mode 100644 packages/console/public/get-started/react/en/step5.md create mode 100644 packages/console/public/get-started/react/zh-CN/index.json create mode 100644 packages/console/public/get-started/react/zh-CN/step1.md create mode 100644 packages/console/public/get-started/react/zh-CN/step2.md create mode 100644 packages/console/public/get-started/react/zh-CN/step3.md create mode 100644 packages/console/public/get-started/react/zh-CN/step4.md create mode 100644 packages/console/public/get-started/react/zh-CN/step5.md create mode 100644 packages/console/src/icons/Arrow.tsx create mode 100644 packages/console/src/icons/Tick.tsx create mode 100644 packages/console/src/include.d/dom.d.ts create mode 100644 packages/console/src/utilities/markdown.ts diff --git a/packages/console/package.json b/packages/console/package.json index 20ca61b73..c8e1e53ed 100644 --- a/packages/console/package.json +++ b/packages/console/package.json @@ -8,10 +8,11 @@ "scripts": { "preinstall": "npx only-allow pnpm", "precommit": "lint-staged", - "start": "parcel src/index.html", - "dev": "PORT=5002 parcel src/index.html --public-url /console --no-hmr --no-cache", + "copyfiles": "copyfiles -u 1 public/**/*.* dist", + "start": "pnpm copyfiles && parcel src/index.html", + "dev": "pnpm copyfiles && PORT=5002 parcel src/index.html --public-url /console --no-hmr --no-cache", "check": "tsc --noEmit", - "build": "pnpm check && rm -rf dist && parcel build src/index.html --no-autoinstall --public-url /console", + "build": "pnpm check && rm -rf dist && pnpm copyfiles && parcel build src/index.html --no-autoinstall --public-url /console", "lint": "eslint --ext .ts --ext .tsx src", "stylelint": "stylelint \"src/**/*.scss\"" }, @@ -50,6 +51,7 @@ "@types/react": "^17.0.14", "@types/react-dom": "^17.0.9", "@types/react-modal": "^3.13.1", + "copyfiles": "^2.4.1", "eslint": "^8.10.0", "lint-staged": "^11.1.1", "parcel": "^2.3.2", diff --git a/packages/console/public/get-started/react/en/index.json b/packages/console/public/get-started/react/en/index.json new file mode 100644 index 000000000..3c62211cb --- /dev/null +++ b/packages/console/public/get-started/react/en/index.json @@ -0,0 +1,3 @@ +{ + "files": ["step1.md", "step2.md", "step3.md", "step4.md", "step5.md"] +} diff --git a/packages/console/public/get-started/react/en/step1.md b/packages/console/public/get-started/react/en/step1.md new file mode 100644 index 000000000..83edd3ff2 --- /dev/null +++ b/packages/console/public/get-started/react/en/step1.md @@ -0,0 +1,34 @@ +--- +title: Install Logto SDK +subtitle: 1 step +--- +## Option 1: Install your SDK dependency + +Run the CLI command under your project's root directory. + +``` +// installation with npm +npm install @logto/react --save + +// installation with yarn +yarn add @logto/react + +// installation with pnpm +pnpm install @logto/react --save +``` + +## Option 2: Add script tag to your HTML + +``` + +``` + +## Option 3: Fork your own from github + +``` +git clone https://github.com/logto-io/js.git +``` + +``` +pnpm build +``` \ No newline at end of file diff --git a/packages/console/public/get-started/react/en/step2.md b/packages/console/public/get-started/react/en/step2.md new file mode 100644 index 000000000..277c5798d --- /dev/null +++ b/packages/console/public/get-started/react/en/step2.md @@ -0,0 +1,35 @@ +--- +title: Initiate LogtoClient +subtitle: 1 step | Lorem ipsum dolor sit amet, consectetuer adipiscing elit. +--- +Add the following code to your main html file. You may need client ID and authorization domain. + +```typescript +import { LogtoProvider, LogtoConfig } from '@logto/react'; +import React from 'react'; + +... + +const App = () => { + const config: LogtoConfig = { clientId: 'foo', endpoint: 'https://your-endpoint-domain.com' } + + return ( + + + + } /> + } /> + + + + } + /> + + + + ); +}; +``` \ No newline at end of file diff --git a/packages/console/public/get-started/react/en/step3.md b/packages/console/public/get-started/react/en/step3.md new file mode 100644 index 000000000..6bb9cdac3 --- /dev/null +++ b/packages/console/public/get-started/react/en/step3.md @@ -0,0 +1,47 @@ +--- +title: Sign In +subtitle: 2 steps +--- +## Step 1: Setup your login + +The Logto React SDK provides you tools and hooks to quickly implement your own authorization flow. First, let’s enter your redirect URI + +```multitextinput +Redirect URI +``` + +Add the following code to your web app + +```typescript +import React from "react"; +import { useLogto } from '@logto/react'; + +const SignInButton = () => { + const { signIn } = useLogto(); + const redirectUrl = window.location.origin + '/callback'; + + return ; +}; + +export default SignInButton; +``` + +## Step 2: Retrieve Auth Status + +```typescript +import React from "react"; +import { useLogto } from '@logto/react'; + +const App = () => { + const { isAuthenticated, signIn } = useLogto(); + + if !(isAuthenticated) { + return + } + + return <> + + + +}; +``` \ No newline at end of file diff --git a/packages/console/public/get-started/react/en/step4.md b/packages/console/public/get-started/react/en/step4.md new file mode 100644 index 000000000..ec789011e --- /dev/null +++ b/packages/console/public/get-started/react/en/step4.md @@ -0,0 +1,26 @@ +--- +title: Sign Out +subtitle: 1 steps +--- +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. + +```multitextinput +Post sign out redirect URI +``` + +Add the following code to your web app + +```typescript +import React from "react"; +import { useLogto } from '@logto/react'; + +const SignOutButton = () => { + const { signOut } = useLogto(); + + return ( + + ); +}; + +export default SignOutButton; +``` \ No newline at end of file diff --git a/packages/console/public/get-started/react/en/step5.md b/packages/console/public/get-started/react/en/step5.md new file mode 100644 index 000000000..481ac231d --- /dev/null +++ b/packages/console/public/get-started/react/en/step5.md @@ -0,0 +1,15 @@ +--- +title: Advanced Settings & Documentation Links +subtitle: 2 steps +--- +## Step 1: Advanced Settings + +Go to application details page and switch to advanced settings tab + +## Step 2: Now check out the documentation links below + +[- SDK Documentation](https://link-url-here.org) + +[- OIDC Documentation](https://link-url-here.org) + +[- Calling API to fetch accessToken](https://link-url-here.org) \ No newline at end of file diff --git a/packages/console/public/get-started/react/zh-CN/index.json b/packages/console/public/get-started/react/zh-CN/index.json new file mode 100644 index 000000000..3c62211cb --- /dev/null +++ b/packages/console/public/get-started/react/zh-CN/index.json @@ -0,0 +1,3 @@ +{ + "files": ["step1.md", "step2.md", "step3.md", "step4.md", "step5.md"] +} diff --git a/packages/console/public/get-started/react/zh-CN/step1.md b/packages/console/public/get-started/react/zh-CN/step1.md new file mode 100644 index 000000000..2ae1000c8 --- /dev/null +++ b/packages/console/public/get-started/react/zh-CN/step1.md @@ -0,0 +1,34 @@ +--- +title: 安装 Logto SDK +subtitle: 1 step +--- +## 方案1: 安装 SDK 依赖包 + +Run the CLI command under your project's root directory. + +``` +// installation with npm +npm install @logto/react --save + +// installation with yarn +yarn add @logto/react + +// installation with pnpm +pnpm install @logto/react --save +``` + +## 方案2: Add script tag to your HTML + +``` + +``` + +## 方案3: Fork your own from github + +``` +git clone https://github.com/logto-io/js.git +``` + +``` +pnpm build +``` \ No newline at end of file diff --git a/packages/console/public/get-started/react/zh-CN/step2.md b/packages/console/public/get-started/react/zh-CN/step2.md new file mode 100644 index 000000000..95b8356ac --- /dev/null +++ b/packages/console/public/get-started/react/zh-CN/step2.md @@ -0,0 +1,35 @@ +--- +title: 初始化 LogtoClient +subtitle: 1 step | Lorem ipsum dolor sit amet, consectetuer adipiscing elit. +--- +Add the following code to your main html file. You may need client ID and authorization domain. + +```typescript +import { LogtoProvider, LogtoConfig } from '@logto/react'; +import React from 'react'; + +... + +const App = () => { + const config: LogtoConfig = { clientId: 'foo', endpoint: 'https://your-endpoint-domain.com' } + + return ( + + + + } /> + } /> + + + + } + /> + + + + ); +}; +``` \ No newline at end of file diff --git a/packages/console/public/get-started/react/zh-CN/step3.md b/packages/console/public/get-started/react/zh-CN/step3.md new file mode 100644 index 000000000..ec589302e --- /dev/null +++ b/packages/console/public/get-started/react/zh-CN/step3.md @@ -0,0 +1,47 @@ +--- +title: 登录 +subtitle: 2 steps +--- +## Step 1: Setup your login + +The Logto React SDK provides you tools and hooks to quickly implement your own authorization flow. First, let’s enter your redirect URI + +```multitextinput +Redirect URI +``` + +Add the following code to your web app + +```typescript +import React from "react"; +import { useLogto } from '@logto/react'; + +const SignInButton = () => { + const { signIn } = useLogto(); + const redirectUrl = window.location.origin + '/callback'; + + return ; +}; + +export default SignInButton; +``` + +## Step 2: Retrieve Auth Status + +```typescript +import React from "react"; +import { useLogto } from '@logto/react'; + +const App = () => { + const { isAuthenticated, signIn } = useLogto(); + + if !(isAuthenticated) { + return + } + + return <> + + + +}; +``` \ No newline at end of file diff --git a/packages/console/public/get-started/react/zh-CN/step4.md b/packages/console/public/get-started/react/zh-CN/step4.md new file mode 100644 index 000000000..4259aa6cd --- /dev/null +++ b/packages/console/public/get-started/react/zh-CN/step4.md @@ -0,0 +1,26 @@ +--- +title: 登出 +subtitle: 1 steps +--- +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. + +```multitextinput +Post sign out redirect URI +``` + +Add the following code to your web app + +```typescript +import React from "react"; +import { useLogto } from '@logto/react'; + +const SignOutButton = () => { + const { signOut } = useLogto(); + + return ( + + ); +}; + +export default SignOutButton; +``` \ No newline at end of file diff --git a/packages/console/public/get-started/react/zh-CN/step5.md b/packages/console/public/get-started/react/zh-CN/step5.md new file mode 100644 index 000000000..bed6491ee --- /dev/null +++ b/packages/console/public/get-started/react/zh-CN/step5.md @@ -0,0 +1,15 @@ +--- +title: 高级设置以及相关文档链接 +subtitle: 2 steps +--- +## Step 1: Advanced Settings + +Go to application details page and switch to advanced settings tab + +## Step 2: Now check out the documentation links below + +[- SDK Documentation](https://link-url-here.org) + +[- OIDC Documentation](https://link-url-here.org) + +[- Calling API to fetch accessToken](https://link-url-here.org) \ No newline at end of file diff --git a/packages/console/src/components/AppContent/index.module.scss b/packages/console/src/components/AppContent/index.module.scss index 9aca295bd..3da1437f0 100644 --- a/packages/console/src/components/AppContent/index.module.scss +++ b/packages/console/src/components/AppContent/index.module.scss @@ -38,6 +38,7 @@ $font-family: 'SF UI Text', 'SF Pro Display', sans-serif; --font-heading-small: 600 24px/32px #{$font-family}; --font-heading: 600 16px/24px #{$font-family}; --font-body: normal 16px/22px #{$font-family}; + --font-body-1: normal 16px/24px #{$font-family}; --font-body-2: normal 14px/20px #{$font-family}; --font-small-text: normal 12px/16px #{$font-family}; --font-caption: normal 14px/20px #{$font-family}; diff --git a/packages/console/src/components/Card/index.module.scss b/packages/console/src/components/Card/index.module.scss index ef7a17b22..312d246e8 100644 --- a/packages/console/src/components/Card/index.module.scss +++ b/packages/console/src/components/Card/index.module.scss @@ -4,5 +4,4 @@ background: var(--color-on-primary); border-radius: _.unit(4); padding: _.unit(6); - min-height: 100%; } diff --git a/packages/console/src/components/Card/index.tsx b/packages/console/src/components/Card/index.tsx index 88c103d23..79275422f 100644 --- a/packages/console/src/components/Card/index.tsx +++ b/packages/console/src/components/Card/index.tsx @@ -1,5 +1,5 @@ import classNames from 'classnames'; -import React, { ReactNode } from 'react'; +import React, { forwardRef, LegacyRef, ReactNode } from 'react'; import * as styles from './index.module.scss'; @@ -8,8 +8,14 @@ type Props = { className?: string; }; -const Card = ({ children, className }: Props) => { - return
{children}
; +const Card = (props: Props, reference?: LegacyRef) => { + const { children, className } = props; + + return ( +
+ {children} +
+ ); }; -export default Card; +export default forwardRef(Card); diff --git a/packages/console/src/components/CardTitle/index.tsx b/packages/console/src/components/CardTitle/index.tsx index 4f04563b2..7f6cb7bb7 100644 --- a/packages/console/src/components/CardTitle/index.tsx +++ b/packages/console/src/components/CardTitle/index.tsx @@ -1,5 +1,5 @@ import { AdminConsoleKey } from '@logto/phrases'; -import React, { ReactNode } from 'react'; +import React from 'react'; import { useTranslation } from 'react-i18next'; import * as styles from './index.module.scss'; @@ -9,32 +9,18 @@ type Props = { subtitle?: AdminConsoleKey; }; -type RawProps = { - title: ReactNode; - subtitle?: ReactNode; -}; - -/** - * Do not use this component directly, unless you do not want to use translation. - */ -export const RawCardTitle = ({ title, subtitle }: RawProps) => ( -
-
{title}
- {subtitle &&
{subtitle}
} -
-); - /** * Always use this component to render CardTitle, with built-in i18n support. */ const CardTitle = ({ title, subtitle }: Props) => { const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); - const props: RawProps = { - title: t(title), - subtitle: subtitle ? t(subtitle) : undefined, - }; - return ; + return ( +
+
{t(title)}
+ {subtitle &&
{t(subtitle)}
} +
+ ); }; export default CardTitle; diff --git a/packages/console/src/hooks/use-api.ts b/packages/console/src/hooks/use-api.ts index d9c4aa107..887505f04 100644 --- a/packages/console/src/hooks/use-api.ts +++ b/packages/console/src/hooks/use-api.ts @@ -19,7 +19,7 @@ export class RequestError extends Error { const toastError = async (response: Response) => { try { - const data = (await response.json()) as RequestErrorBody; + const data = await response.json(); toast.error(data.message || t('admin_console.errors.unknown_server_error')); } catch { toast.error(t('admin_console.errors.unknown_server_error')); diff --git a/packages/console/src/hooks/use-swr-fetcher.ts b/packages/console/src/hooks/use-swr-fetcher.ts index 0771a4850..39a2e72b6 100644 --- a/packages/console/src/hooks/use-swr-fetcher.ts +++ b/packages/console/src/hooks/use-swr-fetcher.ts @@ -16,7 +16,7 @@ const useSwrFetcher = () => { } catch (error: unknown) { if (error instanceof HTTPError) { const { response } = error; - const metadata = (await response.json()) as RequestErrorBody; + const metadata = await response.json(); throw new RequestError(metadata); } throw error; diff --git a/packages/console/src/icons/Arrow.tsx b/packages/console/src/icons/Arrow.tsx new file mode 100644 index 000000000..cb37b0f41 --- /dev/null +++ b/packages/console/src/icons/Arrow.tsx @@ -0,0 +1,31 @@ +import React, { SVGProps } from 'react'; + +export const ArrowDown = (props: SVGProps) => { + return ( + + + + ); +}; + +export const ArrowUp = (props: SVGProps) => { + return ( + + + + ); +}; diff --git a/packages/console/src/icons/Tick.tsx b/packages/console/src/icons/Tick.tsx new file mode 100644 index 000000000..c09f6ed48 --- /dev/null +++ b/packages/console/src/icons/Tick.tsx @@ -0,0 +1,16 @@ +import React, { SVGProps } from 'react'; + +const Tick = (props: SVGProps) => ( + + + +); + +export default Tick; diff --git a/packages/console/src/include.d/dom.d.ts b/packages/console/src/include.d/dom.d.ts new file mode 100644 index 000000000..36ac3e0b4 --- /dev/null +++ b/packages/console/src/include.d/dom.d.ts @@ -0,0 +1,3 @@ +interface Body { + json(): Promise; +} diff --git a/packages/console/src/pages/Applications/components/GetStarted/index.module.scss b/packages/console/src/pages/Applications/components/GetStarted/index.module.scss index 97033b153..028b23bb4 100644 --- a/packages/console/src/pages/Applications/components/GetStarted/index.module.scss +++ b/packages/console/src/pages/Applications/components/GetStarted/index.module.scss @@ -6,6 +6,16 @@ background-color: var(--color-main-background); height: 100vh; + .title { + font: var(--font-title-large); + color: var(--color-component-text); + } + + .subtitle { + font: var(--font-body-1); + color: var(--color-component-caption); + } + .header { display: flex; align-items: center; @@ -17,16 +27,6 @@ margin-left: _.unit(4); } - .title { - font: var(--font-title-large); - color: var(--color-component-text); - } - - .subtitle { - font: var(--font-body-2); - color: var(--color-component-caption); - } - button + button { margin-left: _.unit(4); } @@ -45,26 +45,28 @@ width: 858px; padding: _.unit(6) _.unit(8); display: flex; - align-items: center; + flex-direction: column; + scroll-margin: _.unit(6); &.selector { display: block; padding: _.unit(8); margin-top: _.unit(4); + .subtitle { + color: var(--color-component-text); + margin-top: _.unit(3); + } + .radio { border-radius: _.unit(2); width: 240px; max-width: unset; } - .buttonWrapper { - display: flex; - margin-top: _.unit(8); - } - &.folded { display: flex; + flex-direction: row; align-items: center; flex: 0 0 56px; background: var(--color-neutral-variant-90); @@ -79,20 +81,58 @@ } } } + + &.expanded { + padding: _.unit(8); + } + + .cardHeader { + display: flex; + flex-direction: row; + align-items: center; + cursor: pointer; + + > svg { + fill: var(--color-icon); + } + + .index { + width: 32px; + height: 32px; + border-radius: 50%; + color: var(--color-primary); + background: var(--color-surface-5); + font: var(--font-label-large); + text-align: center; + margin-right: _.unit(4); + + &.active { + color: var(--color-on-primary); + background: var(--color-primary); + } + + &.completed { + background: var(--color-primary); + + > svg { + fill: var(--color-on-primary); + } + } + } + } + + .buttonWrapper { + display: flex; + justify-content: flex-end; + margin-top: _.unit(6); + } } .card + .card { margin-top: _.unit(6); } - .index { - width: 32px; - height: 32px; - border-radius: 50%; - color: var(--color-primary); - background: var(--color-surface-5); - font: var(--font-label-large); - text-align: center; - margin-right: _.unit(4); + .markdownContent { + margin-top: _.unit(6); } } diff --git a/packages/console/src/pages/Applications/components/GetStarted/index.tsx b/packages/console/src/pages/Applications/components/GetStarted/index.tsx index f0e8a4ac4..9539848b5 100644 --- a/packages/console/src/pages/Applications/components/GetStarted/index.tsx +++ b/packages/console/src/pages/Applications/components/GetStarted/index.tsx @@ -1,17 +1,23 @@ import classNames from 'classnames'; -import React, { useMemo, useState } from 'react'; +import i18next from 'i18next'; +import React, { useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; +import ReactMarkdown from 'react-markdown'; +// eslint-disable-next-line node/file-extension-in-import +import useSWRImmutable from 'swr/immutable'; import highFive from '@/assets/images/high-five.svg'; import tada from '@/assets/images/tada.svg'; import Button from '@/components/Button'; import Card from '@/components/Card'; -import CardTitle from '@/components/CardTitle'; import IconButton from '@/components/IconButton'; import RadioGroup, { Radio } from '@/components/RadioGroup'; import Spacer from '@/components/Spacer'; +import { ArrowDown, ArrowUp } from '@/icons/Arrow'; import Close from '@/icons/Close'; +import Tick from '@/icons/Tick'; import { SupportedJavascriptLibraries } from '@/types/applications'; +import { parseMarkdownWithYamlFrontmatter } from '@/utilities/markdown'; import * as styles from './index.module.scss'; @@ -20,10 +26,49 @@ type Props = { onClose: () => void; }; +type DocumentFileNames = { + files: string[]; +}; + +type Step = { + title?: string; + subtitle?: string; + metadata: string; +}; + const GetStarted = ({ appName, onClose }: Props) => { const [isLibrarySelectorFolded, setIsLibrarySelectorFolded] = useState(false); - const [libraryName, setLibraryName] = useState(SupportedJavascriptLibraries.Angular); + const [libraryName, setLibraryName] = useState(SupportedJavascriptLibraries.React); + const [activeStepIndex, setActiveStepIndex] = useState(-1); const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); + const publicPath = useMemo( + () => `/console/get-started/${libraryName}/${i18next.language}`, + [libraryName] + ); + + const { data: jsonData } = useSWRImmutable(`${publicPath}/index.json`); + const { data: steps } = useSWRImmutable(jsonData, async ({ files }: DocumentFileNames) => + Promise.all( + files.map(async (fileName) => { + const response = await fetch(`${publicPath}/${fileName}`); + const markdownFile = await response.text(); + + return parseMarkdownWithYamlFrontmatter(markdownFile); + }) + ) + ); + + const stepReferences = useMemo( + () => Array.from({ length: steps?.length ?? 0 }).map(() => React.createRef()), + [steps?.length] + ); + + useEffect(() => { + if (activeStepIndex > -1) { + const activeStepReference = stepReferences[activeStepIndex]; + activeStepReference?.current?.scrollIntoView({ block: 'start', behavior: 'smooth' }); + } + }, [activeStepIndex, stepReferences]); const onClickFetchSampleProject = () => { window.open( @@ -36,34 +81,28 @@ const GetStarted = ({ appName, onClose }: Props) => { () => ( success - - { - setLibraryName(value); - }} - > +
+
{t('applications.get_started.title')}
+
{t('applications.get_started.subtitle')}
+
+ {Object.values(SupportedJavascriptLibraries).map((library) => ( ))}
-
), - [libraryName] + [libraryName, t] ); const librarySelectorFolded = useMemo( @@ -98,42 +137,70 @@ const GetStarted = ({ appName, onClose }: Props) => {
{isLibrarySelectorFolded ? librarySelectorFolded : librarySelector} - {/* TO-DO: Dynamically render steps from markdown files */} - -
1
- -
- -
2
- -
- -
3
- -
- -
4
- -
- -
5
- -
+ {steps?.map((step, index) => { + const { title, subtitle, metadata } = step; + const isExpanded = activeStepIndex === index; + const isCompleted = activeStepIndex > index; + const isLastStep = index === steps.length - 1; + + // Steps in get-started must have "title" declared in the Yaml header of the markdown source file + if (!title) { + return null; + } + + // TODO: add more styles to markdown renderer + // TODO: render form and input fields in steps + return ( + +
{ + setIsLibrarySelectorFolded(true); + setActiveStepIndex(index); + }} + > +
+ {isCompleted ? : index + 1} +
+
+
{title}
+
{subtitle}
+
+ + {isExpanded ? : } +
+ {isExpanded && ( + <> + {metadata} +
+
+ + )} +
+ ); + })}
); diff --git a/packages/console/src/scss/_colors.scss b/packages/console/src/scss/_colors.scss index f9ffc91e4..dad4541b3 100644 --- a/packages/console/src/scss/_colors.scss +++ b/packages/console/src/scss/_colors.scss @@ -77,6 +77,7 @@ --color-outline: var(--color-neutral-variant-50); --color-disabled: var(--color-neutral-80); --color-readonly: var(--color-neutral-50); + --color-icon: var(--color-neutral-50); --color-border: var(--color-neutral-80); --color-table-row-selected: rgba(25, 28, 29, 4%); --color-code-comment: #66bb6a; diff --git a/packages/console/src/utilities/markdown.ts b/packages/console/src/utilities/markdown.ts new file mode 100644 index 000000000..ce9059976 --- /dev/null +++ b/packages/console/src/utilities/markdown.ts @@ -0,0 +1,39 @@ +type MarkdownWithYamlFrontmatter = { + metadata: string; +} & { + [K in keyof T]?: string; +}; + +/** + * A Yaml frontmatter is a series of variables that are defined at the top of the markdown file, + * that normally is not part of the text contents themselves, such as title, subtitle. + * Yaml frontmatter both starts and ends with three dashes (---), and valid Yaml syntax can be used + * in between the three dashes, to define the variables. + */ +export const parseMarkdownWithYamlFrontmatter = >( + markdown: string +): MarkdownWithYamlFrontmatter => { + const metaRegExp = new RegExp(/^---[\n\r](((?!---).|[\n\r])*)[\n\r]---$/m); + + // "rawYamlHeader" is the full matching string, including the --- and --- + // "metadata" is the first capturing group, which is the string between the --- and --- + const [rawYamlHeader, yamlVariables] = metaRegExp.exec(markdown) ?? []; + + if (!rawYamlHeader || !yamlVariables) { + return { metadata: markdown }; + } + + const keyValues = yamlVariables.split('\n'); + + // Converts a list of string like ["key1: value1", "key2: value2"] to { key1: "value1", key2: "value2" } + const frontmatter = Object.fromEntries( + keyValues.map((keyValue) => { + const splitted = keyValue.split(':'); + const [key, value] = splitted; + + return [key ?? keyValue, value?.trim() ?? '']; + }) + ) as Record; + + return { ...frontmatter, metadata: markdown.replace(rawYamlHeader, '').trim() }; +}; diff --git a/packages/phrases/src/locales/en.ts b/packages/phrases/src/locales/en.ts index 7dae1f93d..9dde34a3f 100644 --- a/packages/phrases/src/locales/en.ts +++ b/packages/phrases/src/locales/en.ts @@ -7,6 +7,7 @@ const translation = { retry: 'Try again', confirm: 'Confirm', cancel: 'Cancel', + done: 'Done', }, sign_in: { action: 'Sign In', @@ -96,16 +97,6 @@ const translation = { 'Now follow the steps below to finish your app settings. Please select the JS library to continue.', description_by_library: 'This quickstart demonstrates how to add Logto to {{library}} application.', - title_step_1: 'Install Logto SDK', - subtitle_step_1: '3 options | lorem ipsum dolor sit amet', - title_step_2: 'Initiate LogtoClient', - subtitle_step_2: '1 step | lorem ipsum dolor sit amet', - title_step_3: 'Sign in', - subtitle_step_3: '1 step | lorem ipsum dolor sit amet', - title_step_4: 'Sign out', - subtitle_step_4: '1 step | lorem ipsum dolor sit amet', - title_step_5: 'Further readings', - subtitle_step_5: 'Tutorial and readings', }, }, application_details: { diff --git a/packages/phrases/src/locales/zh-cn.ts b/packages/phrases/src/locales/zh-cn.ts index 1e62124c4..7fa261cda 100644 --- a/packages/phrases/src/locales/zh-cn.ts +++ b/packages/phrases/src/locales/zh-cn.ts @@ -9,6 +9,7 @@ const translation = { retry: '重试', confirm: '确认', cancel: '取消', + done: '完成', }, sign_in: { action: '登录', @@ -96,16 +97,6 @@ const translation = { title: '恭喜!您的应用已成功创建。', subtitle: '请参考以下步骤完成您的应用设置。首先,请选择您要使用的 Javascript 框架:', description_by_library: '本教程向您演示如何在 {{library}} 应用中集成 Logto 登录功能', - title_step_1: '安装 Logto SDK', - subtitle_step_1: '3 options | lorem ipsum dolor sit amet', - title_step_2: '初始化得到 LogtoClient 实例', - subtitle_step_2: '1 step | lorem ipsum dolor sit amet', - title_step_3: 'Sign in', - subtitle_step_3: '1 step | lorem ipsum dolor sit amet', - title_step_4: 'Sign out', - subtitle_step_4: '1 step | lorem ipsum dolor sit amet', - title_step_5: '延伸阅读', - subtitle_step_5: 'Tutorial and readings', }, }, application_details: { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 52ac0a46f..0a4a86add 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -36,6 +36,7 @@ importers: '@types/react-dom': ^17.0.9 '@types/react-modal': ^3.13.1 classnames: ^2.3.1 + copyfiles: ^2.4.1 csstype: ^3.0.11 eslint: ^8.10.0 i18next: ^21.6.12 @@ -95,6 +96,7 @@ importers: '@types/react': 17.0.37 '@types/react-dom': 17.0.11 '@types/react-modal': 3.13.1 + copyfiles: 2.4.1 eslint: 8.10.0 lint-staged: 11.2.6 parcel: 2.3.2_postcss@8.4.6