0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-16 20:26:19 -05:00

feat(console): feat add SAML application creation (#6857)

feat(console): feat add SAML appliction creation

feat add SAML application creation
This commit is contained in:
simeng-li 2024-12-05 21:58:14 +08:00 committed by GitHub
parent d18422397e
commit c6dc9358c9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 63 additions and 11 deletions

View file

@ -20,7 +20,9 @@ const data = await Promise.all(
return;
}
const logo = ['logo.webp', 'logo.svg', 'logo.png'].find((logo) => existsSync(`${directory}/${logo}`));
const logo = ['logo.webp', 'logo.svg', 'logo.png'].find((logo) =>
existsSync(`${directory}/${logo}`)
);
const darkLogo = ['logo-dark.webp', 'logo-dark.svg', 'logo-dark.png'].find((logo) =>
existsSync(`${directory}/${logo}`)
);
@ -50,7 +52,7 @@ const filename = 'index.tsx';
await fs.writeFile(
filename,
`/* eslint-disable max-lines */
// This is a generated file, don't update manually.\n\nimport { lazy } from 'react';\n\nimport { type Guide } from './types';\n`
// This is a generated file, don't update manually.\n\nimport { safeLazy } from 'react-safe-lazy';\n\nimport { type Guide } from './types';\n`
);
for (const { name, logo, darkLogo } of metadata) {
@ -64,7 +66,10 @@ for (const { name, logo, darkLogo } of metadata) {
if (darkLogo && !darkLogo.endsWith('.svg')) {
// eslint-disable-next-line no-await-in-loop
await fs.appendFile(filename, `import ${camelCase(name)}LogoDark from './${name}/${darkLogo}';\n`);
await fs.appendFile(
filename,
`import ${camelCase(name)}LogoDark from './${name}/${darkLogo}';\n`
);
}
}
@ -73,8 +78,10 @@ await fs.appendFile(filename, 'export const guides: Readonly<Guide[]> = Object.f
const getLogo = ({ name, logo, isDark }) => {
if (!logo) return 'undefined';
if (logo.endsWith('.svg')) return `lazy(async () => import('./${name}/${logo}?react'))`;
return `({ className }: { readonly className?: string }) => <img src={${camelCase(name)}Logo${isDark ? 'Dark' : ''}} alt="${name}" className={className} />`;
if (logo.endsWith('.svg')) return `safeLazy(async () => import('./${name}/${logo}?react'))`;
return `({ className }: { readonly className?: string }) => <img src={${camelCase(name)}Logo${
isDark ? 'Dark' : ''
}} alt="${name}" className={className} />`;
};
for (const { name, logo, darkLogo, order } of metadata) {
@ -87,11 +94,14 @@ for (const { name, logo, darkLogo, order } of metadata) {
id: '${name}',
Logo: ${getLogo({ name, logo })},
DarkLogo: ${getLogo({ name, logo: darkLogo, isDark: true })},
Component: lazy(async () => import('./${name}/README.mdx')),
Component: safeLazy(async () => import('./${name}/README.mdx')),
metadata: ${camelCase(name)},
},`
);
}
await fs.appendFile(filename, `]);
/* eslint-enable max-lines */\n`);
await fs.appendFile(
filename,
`]);
/* eslint-enable max-lines */\n`
);

View file

@ -1,5 +1,6 @@
/* eslint-disable max-lines */
// This is a generated file, don't update manually.
import { safeLazy } from 'react-safe-lazy';
import apiExpress from './api-express/index';
@ -12,6 +13,7 @@ import nativeExpo from './native-expo/index';
import nativeFlutter from './native-flutter/index';
import nativeIosSwift from './native-ios-swift/index';
import protectedApp from './protected-app/index';
import samlIdp from './saml-idp/index';
import spaAngular from './spa-angular/index';
import spaChromeExtension from './spa-chrome-extension/index';
import spaReact from './spa-react/index';
@ -321,6 +323,14 @@ export const guides: Readonly<Guide[]> = Object.freeze([
Component: safeLazy(async () => import('./api-spring-boot/README.mdx')),
metadata: apiSpringBoot,
},
{
order: Number.POSITIVE_INFINITY,
id: 'saml-idp',
Logo: safeLazy(async () => import('./saml-idp/logo.svg?react')),
DarkLogo: undefined,
Component: safeLazy(async () => import('./saml-idp/README.mdx')),
metadata: samlIdp,
},
{
order: Number.POSITIVE_INFINITY,
id: 'third-party-oidc',

View file

@ -0,0 +1 @@
# Place holder for SAML IdP guide

View file

@ -0,0 +1,15 @@
import { ApplicationType } from '@logto/schemas';
import { type GuideMetadata } from '../types';
const metadata = Object.freeze({
name: 'SAML',
description: 'Use Logto as a SAML identity provider (IdP) for your application.',
target: ApplicationType.SAML,
isThirdParty: true,
skipGuideAfterCreation: true,
isCloud: true,
isDevFeature: true,
} satisfies GuideMetadata);
export default metadata;

View file

@ -0,0 +1,6 @@
<svg width="36" height="36" viewBox="0 0 36 36" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path d="M0 30.1483C0 26.1948 3.77151 11.4452 11.5694 1.56152C6.065 11.9521 2.86307 18.9874 5.19857 24.4715C7.03336 28.7797 16.4621 27.4112 21.5588 25.6879C12.4868 29.7935 3.17691 30.351 0 30.1483Z" fill="#C1272D"/>
<path d="M16.8907 1.125C20.3335 3.10174 31.292 13.7247 36 25.3825C29.7038 15.4466 25.1783 9.17124 19.2349 8.4407C14.5658 7.86678 11.0431 16.6716 9.99548 21.9228C10.9563 12.0567 15.1257 3.75976 16.8907 1.125Z" fill="#C1272D"/>
<path d="M33.798 30.1136C30.3552 32.0903 15.6253 36.2169 3.11938 34.4428C14.9199 33.9881 22.6474 33.2282 26.2552 28.4747C29.0896 24.7403 23.1835 17.304 19.1345 13.7761C27.2457 19.5367 32.3861 27.2761 33.798 30.1136Z" fill="#C1272D"/>
</svg>

After

Width:  |  Height:  |  Size: 787 B

View file

@ -40,6 +40,8 @@ export type GuideMetadata = {
title: string;
url: URL;
}>;
/** Whether the guide is a development feature, that needs to be hidden from the production environment. */
isDevFeature?: boolean;
};
/** The guide instance to build in the console. */

View file

@ -10,6 +10,7 @@ import Modal from 'react-modal';
import { useSWRConfig } from 'swr';
import { GtagConversionId, reportConversion } from '@/components/Conversion/utils';
import { isDevFeaturesEnabled } from '@/consts/env';
import { SubscriptionDataContext } from '@/contexts/SubscriptionDataProvider';
import DynamicT from '@/ds-components/DynamicT';
import FormField from '@/ds-components/FormField';
@ -91,7 +92,13 @@ function CreateForm({
return;
}
const createdApp = await api.post('api/applications', { json: data }).json<Application>();
const appCreationEndpoint =
// TODO: @darcy remove this after the SAML is implemented
isDevFeaturesEnabled && data.type === ApplicationType.SAML
? 'api/saml-applications'
: 'api/applications';
const createdApp = await api.post(appCreationEndpoint, { json: data }).json<Application>();
// Report the conversion event after the application is created. Note that the conversion
// should be set as count once since this will be reported multiple times.

View file

@ -3,7 +3,7 @@ import { useCallback, useMemo } from 'react';
import { guides } from '@/assets/docs/guides';
import { type Guide } from '@/assets/docs/guides/types';
import { isCloud as isCloudEnv } from '@/consts/env';
import { isCloud as isCloudEnv, isDevFeaturesEnabled } from '@/consts/env';
import {
thirdPartyAppCategory,
type AppGuideCategory,
@ -37,7 +37,8 @@ export const useAppGuideMetadata = (): {
const appGuides = useMemo(
() =>
guides.filter(
({ metadata: { target, isCloud } }) => target !== 'API' && (isCloudEnv || !isCloud)
({ metadata: { target, isCloud, isDevFeature } }) =>
target !== 'API' && (isCloudEnv || !isCloud) && (isDevFeaturesEnabled || !isDevFeature)
),
[]
);