From 106e4e2a528b57dd75594e0203f95e0814cb08bf Mon Sep 17 00:00:00 2001 From: Brion Date: Mon, 9 Dec 2024 15:09:29 +0530 Subject: [PATCH 01/35] Refactor core elements usage --- .../components/decorated-visual-flow.tsx | 2 +- .../components/visual-flow.tsx | 20 ++++++++++++------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/features/admin.flow-builder-core.v1/components/decorated-visual-flow.tsx b/features/admin.flow-builder-core.v1/components/decorated-visual-flow.tsx index e34cbdae502..e3bba6a1585 100644 --- a/features/admin.flow-builder-core.v1/components/decorated-visual-flow.tsx +++ b/features/admin.flow-builder-core.v1/components/decorated-visual-flow.tsx @@ -62,7 +62,7 @@ const DecoratedVisualFlow: FunctionComponent - + diff --git a/features/admin.flow-builder-core.v1/components/visual-flow.tsx b/features/admin.flow-builder-core.v1/components/visual-flow.tsx index 65ea39fd6f7..c3ac92f2f09 100644 --- a/features/admin.flow-builder-core.v1/components/visual-flow.tsx +++ b/features/admin.flow-builder-core.v1/components/visual-flow.tsx @@ -41,9 +41,8 @@ import { } from "@xyflow/react"; import React, { DragEvent, FC, FunctionComponent, ReactElement, useCallback, useMemo } from "react"; import NodeFactory from "./elements/nodes/node-factory"; -import useGetFlowBuilderCoreElements from "../api/use-get-flow-builder-core-elements"; import useAuthenticationFlowBuilderCore from "../hooks/use-authentication-flow-builder-core-context"; -import { ElementCategories } from "../models/elements"; +import { ElementCategories, Elements } from "../models/elements"; import { Node } from "../models/node"; import "@xyflow/react/dist/style.css"; import "./visual-flow.scss"; @@ -51,7 +50,12 @@ import "./visual-flow.scss"; /** * Props interface of {@link VisualFlow} */ -export type VisualFlowPropsInterface = IdentifiableComponentInterface & ReactFlowProps; +export interface VisualFlowPropsInterface extends IdentifiableComponentInterface, ReactFlowProps { + /** + * Flow elements. + */ + elements: Elements; +} /** * Wrapper component for React Flow used in the Visual Editor. @@ -61,6 +65,7 @@ export type VisualFlowPropsInterface = IdentifiableComponentInterface & ReactFlo */ const VisualFlow: FunctionComponent = ({ "data-componentid": componentId = "authentication-flow-visual-flow", + elements, ...rest }: VisualFlowPropsInterface): ReactElement => { const [ nodes, setNodes, onNodesChange ] = useNodesState([]); @@ -68,7 +73,6 @@ const VisualFlow: FunctionComponent = ({ const { screenToFlowPosition, toObject } = useReactFlow(); const { node, generateComponentId } = useDnD(); const { onElementDropOnCanvas } = useAuthenticationFlowBuilderCore(); - const { data: coreElements } = useGetFlowBuilderCoreElements(); const onDragOver: (event: DragEvent) => void = useCallback((event: DragEvent) => { event.preventDefault(); @@ -138,15 +142,17 @@ const VisualFlow: FunctionComponent = ({ // TODO: Handle the submit const handlePublish = (): void => { - const _flow: any = toObject(); + const flow: any = toObject(); + + console.log(JSON.stringify(flow, null, 2)); }; const generateNodeTypes = () => { - if (!coreElements?.nodes) { + if (!elements?.nodes) { return {}; } - return coreElements.nodes.reduce((acc: Record>, node: Node) => { + return elements.nodes.reduce((acc: Record>, node: Node) => { acc[node.type] = (props: any) => ; return acc; From 33621f0e73c7ad45cbf3ab1ab7a54f473ed16af7 Mon Sep 17 00:00:00 2001 From: Brion Date: Mon, 9 Dec 2024 15:09:37 +0530 Subject: [PATCH 02/35] Introduce flow actions --- .../api/use-get-flow-builder-core-actions.ts | 45 ++++++++++++++++ .../data/actions.json | 47 +++++++++++++++++ .../models/actions.ts | 19 ++++++- ...e-get-registration-flow-builder-actions.ts | 52 +++++++++++++++++++ .../element-properties.tsx | 14 +++++ .../button-extended-properties.tsx | 27 +++++----- .../data/actions.json | 1 + 7 files changed, 189 insertions(+), 16 deletions(-) create mode 100644 features/admin.flow-builder-core.v1/api/use-get-flow-builder-core-actions.ts create mode 100644 features/admin.flow-builder-core.v1/data/actions.json create mode 100644 features/admin.registration-flow-builder.v1/api/use-get-registration-flow-builder-actions.ts create mode 100644 features/admin.registration-flow-builder.v1/data/actions.json diff --git a/features/admin.flow-builder-core.v1/api/use-get-flow-builder-core-actions.ts b/features/admin.flow-builder-core.v1/api/use-get-flow-builder-core-actions.ts new file mode 100644 index 00000000000..67c533a7203 --- /dev/null +++ b/features/admin.flow-builder-core.v1/api/use-get-flow-builder-core-actions.ts @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { RequestErrorInterface, RequestResultInterface } from "@wso2is/admin.core.v1/hooks/use-request"; +import actions from "../data/actions.json";; +import { Actions } from "../models/actions"; + +/** + * Hook to get the actions supported by the flow builder. + * + * This function calls the GET method of the following endpoint to get the elements. + * - TODO: Fill this + * For more details, refer to the documentation: + * {@link https://TODO:)} + * + * @returns SWR response object containing the data, error, isLoading, isValidating, mutate. + */ +const useGetFlowBuilderCoreActions = ( + _shouldFetch: boolean = true +): RequestResultInterface => { + return { + data: (actions as unknown) as Data, + error: null, + isLoading: false, + isValidating: false, + mutate: () => null + }; +}; + +export default useGetFlowBuilderCoreActions; diff --git a/features/admin.flow-builder-core.v1/data/actions.json b/features/admin.flow-builder-core.v1/data/actions.json new file mode 100644 index 00000000000..8c09d59991f --- /dev/null +++ b/features/admin.flow-builder-core.v1/data/actions.json @@ -0,0 +1,47 @@ +[ + { + "category": "NAVIGATION", + "actions": [ + { + "type": "NEXT", + "displayName": "Next" + }, + { + "type": "PREVIOUS", + "displayName": "Previous" + } + ] + }, + { + "category": "VERIFICATION", + "actions": [ + { + "type": "NEXT", + "meta": { + "actionType": "VERIFICATION" + }, + "displayName": "Verify email address" + }, + { + "type": "NEXT", + "meta": { + "actionType": "VERIFICATION" + }, + "displayName": "Verify mobile number" + } + ] + }, + { + "category": "SOCIAL", + "actions": [ + { + "type": "EXECUTOR", + "name": "GoogleOIDCAuthenticator", + "meta": { + "idp": "Google" + }, + "displayName": "Continue with Google Sign Up" + } + ] + } +] diff --git a/features/admin.flow-builder-core.v1/models/actions.ts b/features/admin.flow-builder-core.v1/models/actions.ts index 40a58e12b01..ecbc0512810 100644 --- a/features/admin.flow-builder-core.v1/models/actions.ts +++ b/features/admin.flow-builder-core.v1/models/actions.ts @@ -16,8 +16,25 @@ * under the License. */ +export interface Action { + category: ActionCategories; + actions: { + type: ActionTypes; + displayName: string; + meta?: Record; + } +} + +export type Actions = Action[]; + +export enum ActionCategories { + Navigation = "NAVIGATION", + Verification = "VERIFICATION", + Executor = "SOCIAL" +} + export enum ActionTypes { Next = "NEXT", Previous = "PREVIOUS", - Submit = "SUBMIT" + Executor = "EXECUTOR" } diff --git a/features/admin.registration-flow-builder.v1/api/use-get-registration-flow-builder-actions.ts b/features/admin.registration-flow-builder.v1/api/use-get-registration-flow-builder-actions.ts new file mode 100644 index 00000000000..c1b3991ad1e --- /dev/null +++ b/features/admin.registration-flow-builder.v1/api/use-get-registration-flow-builder-actions.ts @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { RequestErrorInterface, RequestResultInterface } from "@wso2is/admin.core.v1/hooks/use-request"; +import useGetFlowBuilderCoreActions from "@wso2is/admin.flow-builder-core.v1/api/use-get-flow-builder-core-actions"; +import { Actions } from "@wso2is/admin.flow-builder-core.v1/models/actions"; +import actions from "../data/actions.json"; + +/** + * Hook to get the actions supported by the flow builder. + * This hook will aggregate the core actions and the registration specific actions. + * + * This function calls the GET method of the following endpoint to get the actions. + * - TODO: Fill this + * For more details, refer to the documentation: + * {@link https://TODO:)} + * + * @returns SWR response object containing the data, error, isLoading, isValidating, mutate. + */ +const useGetRegistrationFlowBuilderActions = ( + _shouldFetch: boolean = true +): RequestResultInterface => { + const { data: coreActions } = useGetFlowBuilderCoreActions(); + + return { + data: ([ + ...coreActions, + ...actions + ] as unknown) as Data, + error: null, + isLoading: false, + isValidating: false, + mutate: () => null + }; +}; + +export default useGetRegistrationFlowBuilderActions; diff --git a/features/admin.registration-flow-builder.v1/components/element-property-panel/element-properties.tsx b/features/admin.registration-flow-builder.v1/components/element-property-panel/element-properties.tsx index 0476a63e0e3..67c4e67650b 100644 --- a/features/admin.registration-flow-builder.v1/components/element-property-panel/element-properties.tsx +++ b/features/admin.registration-flow-builder.v1/components/element-property-panel/element-properties.tsx @@ -21,6 +21,7 @@ import { Element, ElementCategories } from "@wso2is/admin.flow-builder-core.v1/m import { IdentifiableComponentInterface } from "@wso2is/core/models"; import React, { FunctionComponent, ReactElement } from "react"; import ElementPropertyFactory from "./element-property-factory"; +import ButtonExtendedProperties from "./extended-properties/button-extended-properties"; import FieldExtendedProperties from "./extended-properties/field-extended-properties"; /** @@ -80,6 +81,19 @@ const ElementProperties: FunctionComponent = ({ { renderElementPropertyFactory() } ); + case ElementCategories.Action: + return ( + <> + + { renderElementPropertyFactory() } + + ); default: return <>{ renderElementPropertyFactory() }; } diff --git a/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/button-extended-properties.tsx b/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/button-extended-properties.tsx index dbf3010da19..867a3e7df7a 100644 --- a/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/button-extended-properties.tsx +++ b/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/button-extended-properties.tsx @@ -16,14 +16,16 @@ * under the License. */ -import FormControl from "@oxygen-ui/react/FormControl"; -import Select from "@oxygen-ui/react/Select"; +import Box from "@oxygen-ui/react/Box"; import Stack from "@oxygen-ui/react/Stack"; +import Typography from "@oxygen-ui/react/Typography"; // eslint-disable-next-line max-len import { CommonComponentPropertyFactoryPropsInterface } from "@wso2is/admin.flow-builder-core.v1/components/element-property-panel/common-component-property-factory"; +import { Action } from "@wso2is/admin.flow-builder-core.v1/models/actions"; import { IdentifiableComponentInterface } from "@wso2is/core/models"; -import React, { FunctionComponent, ReactElement, useState } from "react"; -import { RegistrationFlowActionTypes } from "../../../models/actions"; +import capitalize from "lodash-es/capitalize"; +import React, { FunctionComponent, ReactElement } from "react"; +import useGetRegistrationFlowCoreActions from "../../../api/use-get-registration-flow-builder-actions"; /** * Props interface of {@link ButtonExtendedProperties} @@ -40,20 +42,15 @@ export type ButtonExtendedPropertiesPropsInterface = CommonComponentPropertyFact const ButtonExtendedProperties: FunctionComponent = ({ "data-componentid": componentId = "button-extended-properties" }: ButtonExtendedPropertiesPropsInterface): ReactElement => { - const [ selectedActionType ] = useState(null); + const { data: actions } = useGetRegistrationFlowCoreActions(); return ( - - - + { actions?.map((action: Action, index: number) => ( + + { capitalize(action?.category) } + + )) } ); }; diff --git a/features/admin.registration-flow-builder.v1/data/actions.json b/features/admin.registration-flow-builder.v1/data/actions.json new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/features/admin.registration-flow-builder.v1/data/actions.json @@ -0,0 +1 @@ +[] From 5bf8cc6da281d5712b74e7dc69a425de62ad5e62 Mon Sep 17 00:00:00 2001 From: Brion Date: Thu, 12 Dec 2024 14:27:32 +0530 Subject: [PATCH 03/35] Update element properties --- .../element-properties.tsx | 30 +---------- .../components/adapters/button-adapter.scss | 35 ++++++++++++ .../components/adapters/button-adapter.tsx | 13 ++++- .../adapters/typography-adapter.tsx | 35 ++++++++---- .../models/actions.ts | 16 ++++-- .../models/component.ts | 1 + .../element-properties.tsx | 53 ++++++++++++++----- .../button-extended-properties.scss | 41 ++++++++++++++ .../button-extended-properties.tsx | 45 +++++++++++++--- .../field-extended-properties.tsx | 46 +++++++--------- 10 files changed, 223 insertions(+), 92 deletions(-) create mode 100644 features/admin.flow-builder-core.v1/components/elements/components/adapters/button-adapter.scss create mode 100644 features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/button-extended-properties.scss diff --git a/features/admin.flow-builder-core.v1/components/element-property-panel/element-properties.tsx b/features/admin.flow-builder-core.v1/components/element-property-panel/element-properties.tsx index 58cbda17d83..99e237a8484 100644 --- a/features/admin.flow-builder-core.v1/components/element-property-panel/element-properties.tsx +++ b/features/admin.flow-builder-core.v1/components/element-property-panel/element-properties.tsx @@ -16,18 +16,12 @@ * under the License. */ -import FormControl from "@oxygen-ui/react/FormControl"; -import MenuItem from "@oxygen-ui/react/MenuItem"; -import Select from "@oxygen-ui/react/Select"; import Stack from "@oxygen-ui/react/Stack"; import Typography from "@oxygen-ui/react/Typography"; import { IdentifiableComponentInterface } from "@wso2is/core/models"; import { useReactFlow } from "@xyflow/react"; -import capitalize from "lodash-es/capitalize"; -import isEmpty from "lodash-es/isEmpty"; -import React, { ChangeEvent, FunctionComponent, HTMLAttributes, ReactElement } from "react"; +import React, { FunctionComponent, HTMLAttributes, ReactElement } from "react"; import useAuthenticationFlowBuilderCore from "../../hooks/use-authentication-flow-builder-core-context"; -import { Base } from "../../models/base"; import { Component } from "../../models/component"; import { Element } from "../../models/elements"; @@ -54,8 +48,6 @@ const ElementProperties: FunctionComponent = ({ lastInteractedNodeId } = useAuthenticationFlowBuilderCore(); - const hasVariants: boolean = !isEmpty(lastInteractedElement?.variants); - const changeSelectedVariant = (selected: string) => { const selectedVariant: Component = lastInteractedElement?.variants?.find( (element: Component) => element.variant === selected @@ -104,30 +96,12 @@ const ElementProperties: FunctionComponent = ({
{ lastInteractedElement ? ( - { hasVariants && ( - - - - ) } { lastInteractedElement && ( ) } diff --git a/features/admin.flow-builder-core.v1/components/elements/components/adapters/button-adapter.scss b/features/admin.flow-builder-core.v1/components/elements/components/adapters/button-adapter.scss new file mode 100644 index 00000000000..7020292db58 --- /dev/null +++ b/features/admin.flow-builder-core.v1/components/elements/components/adapters/button-adapter.scss @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +.social-button.oxygen-button { + background: var(--oxygen-palette-common-white); + color: var(--oxygen-palette-common-black); + width: 100%; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 0 1px 0 rgba(0, 0, 0, .12), 0 1px 1px 0 rgba(0, 0, 0, .24); + + &:hover { + color: rgba(0, 0, 0, .8); + } + + .oxygen-sign-in-option-image { + height: 20px; + } +} diff --git a/features/admin.flow-builder-core.v1/components/elements/components/adapters/button-adapter.tsx b/features/admin.flow-builder-core.v1/components/elements/components/adapters/button-adapter.tsx index 2988429c512..0da7b334cd9 100644 --- a/features/admin.flow-builder-core.v1/components/elements/components/adapters/button-adapter.tsx +++ b/features/admin.flow-builder-core.v1/components/elements/components/adapters/button-adapter.tsx @@ -20,6 +20,7 @@ import Button, { ButtonProps } from "@oxygen-ui/react/Button"; import { IdentifiableComponentInterface } from "@wso2is/core/models"; import React, { FunctionComponent, ReactElement } from "react"; import { ButtonVariants, Component } from "../../../../models/component"; +import "./button-adapter.scss"; /** * Props interface of {@link ButtonAdapter} @@ -50,23 +51,33 @@ export const ButtonAdapter: FunctionComponent = ({ config = { ...config, color: "primary", + fullWidth: true, variant: "contained" }; } else if (node.variant === ButtonVariants.Secondary) { config = { ...config, color: "secondary", + fullWidth: true, variant: "contained" }; } else if (node.variant === ButtonVariants.Text) { config = { ...config, + fullWidth: true, variant: "text" }; + } else if (node.variant === ButtonVariants.Social) { + config = { + ...config, + className: "social-button", + fullWidth: true, + variant: "contained" + }; } return ( - ); diff --git a/features/admin.flow-builder-core.v1/components/elements/components/adapters/typography-adapter.tsx b/features/admin.flow-builder-core.v1/components/elements/components/adapters/typography-adapter.tsx index 4aa4955a958..4705b4aee85 100644 --- a/features/admin.flow-builder-core.v1/components/elements/components/adapters/typography-adapter.tsx +++ b/features/admin.flow-builder-core.v1/components/elements/components/adapters/typography-adapter.tsx @@ -16,10 +16,10 @@ * under the License. */ -import Typography from "@oxygen-ui/react/Typography"; +import Typography, { TypographyProps } from "@oxygen-ui/react/Typography"; import { IdentifiableComponentInterface } from "@wso2is/core/models"; import React, { FunctionComponent, ReactElement } from "react"; -import { Component } from "../../../../models/component"; +import { Component, TypographyVariants } from "../../../../models/component"; /** * Props interface of {@link TypographyAdapter} @@ -43,13 +43,28 @@ export interface TypographyAdapterPropsInterface extends IdentifiableComponentIn */ export const TypographyAdapter: FunctionComponent = ({ node -}: TypographyAdapterPropsInterface): ReactElement => ( - - { node?.config?.field?.text } - -); +}: TypographyAdapterPropsInterface): ReactElement => { + let config: TypographyProps = {}; + + if ( + node?.variant === TypographyVariants.H1 || + node?.variant === TypographyVariants.H2 || + node?.variant === TypographyVariants.H3 || + node?.variant === TypographyVariants.H4 || + node?.variant === TypographyVariants.H5 || + node?.variant === TypographyVariants.H6 + ) { + config = { + ...config, + textAlign: "center" + }; + } + + return ( + + { node?.config?.field?.text } + + ); +}; export default TypographyAdapter; diff --git a/features/admin.flow-builder-core.v1/models/actions.ts b/features/admin.flow-builder-core.v1/models/actions.ts index ecbc0512810..d1d2a9bbb21 100644 --- a/features/admin.flow-builder-core.v1/models/actions.ts +++ b/features/admin.flow-builder-core.v1/models/actions.ts @@ -16,13 +16,18 @@ * under the License. */ +import { BaseDisplay } from "./base"; + export interface Action { category: ActionCategories; - actions: { - type: ActionTypes; - displayName: string; - meta?: Record; - } + display: BaseDisplay; + types: ActionType[]; +} + +export interface ActionType { + type: ActionTypes; + display: BaseDisplay; + meta?: Record; } export type Actions = Action[]; @@ -30,6 +35,7 @@ export type Actions = Action[]; export enum ActionCategories { Navigation = "NAVIGATION", Verification = "VERIFICATION", + CredentialOnboarding = "CREDENTIAL_ONBOARDING", Executor = "SOCIAL" } diff --git a/features/admin.flow-builder-core.v1/models/component.ts b/features/admin.flow-builder-core.v1/models/component.ts index 176fce7d9dd..8200863fa28 100644 --- a/features/admin.flow-builder-core.v1/models/component.ts +++ b/features/admin.flow-builder-core.v1/models/component.ts @@ -45,6 +45,7 @@ export enum InputVariants { export enum ButtonVariants { Primary = "PRIMARY", Secondary = "SECONDARY", + Social = "SOCIAL", Text = "TEXT" } diff --git a/features/admin.registration-flow-builder.v1/components/element-property-panel/element-properties.tsx b/features/admin.registration-flow-builder.v1/components/element-property-panel/element-properties.tsx index 67c4e67650b..baa4a16faee 100644 --- a/features/admin.registration-flow-builder.v1/components/element-property-panel/element-properties.tsx +++ b/features/admin.registration-flow-builder.v1/components/element-property-panel/element-properties.tsx @@ -16,10 +16,13 @@ * under the License. */ +import Autocomplete, { AutocompleteRenderInputParams } from "@oxygen-ui/react/Autocomplete"; +import TextField from "@oxygen-ui/react/TextField"; import { FieldKey, FieldValue, Properties } from "@wso2is/admin.flow-builder-core.v1/models/base"; import { Element, ElementCategories } from "@wso2is/admin.flow-builder-core.v1/models/elements"; import { IdentifiableComponentInterface } from "@wso2is/core/models"; -import React, { FunctionComponent, ReactElement } from "react"; +import isEmpty from "lodash-es/isEmpty"; +import React, { ChangeEvent, FunctionComponent, ReactElement } from "react"; import ElementPropertyFactory from "./element-property-factory"; import ButtonExtendedProperties from "./extended-properties/button-extended-properties"; import FieldExtendedProperties from "./extended-properties/field-extended-properties"; @@ -41,6 +44,11 @@ export interface ElementPropertiesPropsInterface extends IdentifiableComponentIn * @param element - The element associated with the property. */ onChange: (propertyKey: string, previousValue: any, newValue: any, element: Element) => void; + /** + * The event handler for the variant change. + * @param variant - The variant of the element. + */ + onVariantChange: (variant: string) => void; } /** @@ -52,19 +60,40 @@ export interface ElementPropertiesPropsInterface extends IdentifiableComponentIn const ElementProperties: FunctionComponent = ({ properties, element, - onChange + onChange, + onVariantChange }: ElementPropertiesPropsInterface): ReactElement | null => { const renderElementPropertyFactory = () => { - return Object.entries(properties).map(([ key, value ]: [FieldKey, FieldValue]) => ( - - )); + const hasVariants: boolean = !isEmpty(element?.variants); + + return ( + <> + { hasVariants && ( + variant.variant } + renderInput={ (params: AutocompleteRenderInputParams) => ( + + ) } + onChange={ (_: ChangeEvent, variant: Element) => { + onVariantChange(variant?.variant); + } } + /> + ) } + { Object.entries(properties).map(([ key, value ]: [FieldKey, FieldValue]) => ( + + )) } + + ); }; switch (element.category) { diff --git a/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/button-extended-properties.scss b/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/button-extended-properties.scss new file mode 100644 index 00000000000..a7caad0f350 --- /dev/null +++ b/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/button-extended-properties.scss @@ -0,0 +1,41 @@ +.button-extended-properties { + .extended-property.action-type { + &.oxygen-card { + // TODO: `@oxygen-ui/react/Card` declares a default padding which is a bug. + // Remove this once it is handled. + // Tracker: https://github.com/wso2/oxygen-ui/issues/300 + padding: 0; + + .oxygen-card-content:last-child { + padding: var(--oxygen-spacing-1); + + &:last-child { + padding-bottom: var(--oxygen-spacing-1); + } + } + } + + .action-type-name { + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + line-clamp: 2; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + } + + .action-type-icon { + background-color: var(--oxygen-palette-grey-300); + + img { + height: 18px; + width: 18px; + } + } + } + + .button-extended-properties-sub-heading { + margin-top: var(--oxygen-spacing-2); + margin-bottom: var(--oxygen-spacing-1); + } +} diff --git a/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/button-extended-properties.tsx b/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/button-extended-properties.tsx index 867a3e7df7a..435e1a9c28c 100644 --- a/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/button-extended-properties.tsx +++ b/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/button-extended-properties.tsx @@ -16,16 +16,20 @@ * under the License. */ +import Avatar from "@oxygen-ui/react/Avatar"; import Box from "@oxygen-ui/react/Box"; +import Card from "@oxygen-ui/react/Card"; +import CardContent from "@oxygen-ui/react/CardContent"; +import Grid from "@oxygen-ui/react/Grid"; import Stack from "@oxygen-ui/react/Stack"; import Typography from "@oxygen-ui/react/Typography"; // eslint-disable-next-line max-len import { CommonComponentPropertyFactoryPropsInterface } from "@wso2is/admin.flow-builder-core.v1/components/element-property-panel/common-component-property-factory"; -import { Action } from "@wso2is/admin.flow-builder-core.v1/models/actions"; +import { Action, ActionType } from "@wso2is/admin.flow-builder-core.v1/models/actions"; import { IdentifiableComponentInterface } from "@wso2is/core/models"; -import capitalize from "lodash-es/capitalize"; import React, { FunctionComponent, ReactElement } from "react"; import useGetRegistrationFlowCoreActions from "../../../api/use-get-registration-flow-builder-actions"; +import "./button-extended-properties.scss"; /** * Props interface of {@link ButtonExtendedProperties} @@ -45,12 +49,37 @@ const ButtonExtendedProperties: FunctionComponent - { actions?.map((action: Action, index: number) => ( - - { capitalize(action?.category) } - - )) } + +
+ Action Type + { actions?.map((action: Action, index: number) => ( + + + { action?.display?.label } + + + { action.types?.map((type: ActionType, typeIndex: number) => ( + + + + + + + { type?.display?.label } + + + + + + )) } + + + )) } +
); }; diff --git a/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/field-extended-properties.tsx b/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/field-extended-properties.tsx index fcadefc27c2..c407572da2d 100644 --- a/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/field-extended-properties.tsx +++ b/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/field-extended-properties.tsx @@ -16,12 +16,12 @@ * under the License. */ -import FormControl from "@oxygen-ui/react/FormControl"; -import MenuItem from "@oxygen-ui/react/MenuItem"; -import Select from "@oxygen-ui/react/Select"; +import Autocomplete, { AutocompleteRenderInputParams } from "@oxygen-ui/react/Autocomplete"; import Stack from "@oxygen-ui/react/Stack"; -// eslint-disable-next-line max-len -import { CommonComponentPropertyFactoryPropsInterface } from "@wso2is/admin.flow-builder-core.v1/components/element-property-panel/common-component-property-factory"; +import TextField from "@oxygen-ui/react/TextField"; +import { + CommonComponentPropertyFactoryPropsInterface +} from "@wso2is/admin.flow-builder-core.v1/components/element-property-panel/common-component-property-factory"; import { IdentifiableComponentInterface } from "@wso2is/core/models"; import React, { ChangeEvent, FunctionComponent, ReactElement, useState } from "react"; import useGetSupportedProfileAttributes from "../../../api/use-get-supported-profile-attributes"; @@ -49,30 +49,20 @@ const FieldExtendedProperties: FunctionComponent - - - + setSelectedAttribute( + attributes?.find((attribute: Attribute) => attribute?.claimURI === attribute?.claimURI) + ); + } } + /> ); }; From 356af7b189b924e881a36965d1dbdf6572cf90a0 Mon Sep 17 00:00:00 2001 From: Brion Date: Thu, 12 Dec 2024 15:39:27 +0530 Subject: [PATCH 04/35] Update schema --- .../data/actions.json | 63 +++++- .../data/components.json | 145 ++++--------- .../data/nodes.json | 10 +- .../data/payload.json | 194 +++++------------- .../data/widgets.json | 13 +- 5 files changed, 152 insertions(+), 273 deletions(-) diff --git a/features/admin.flow-builder-core.v1/data/actions.json b/features/admin.flow-builder-core.v1/data/actions.json index 8c09d59991f..5d1f6c3899c 100644 --- a/features/admin.flow-builder-core.v1/data/actions.json +++ b/features/admin.flow-builder-core.v1/data/actions.json @@ -1,46 +1,93 @@ [ { "category": "NAVIGATION", - "actions": [ + "display": { + "label": "Navigation", + "image": "" + }, + "types": [ { "type": "NEXT", - "displayName": "Next" + "display": { + "label": "Next", + "image": "https://www.svgrepo.com/show/305382/arrowhead-right-outline.svg" + } }, { "type": "PREVIOUS", - "displayName": "Previous" + "display": { + "label": "Previous", + "image": "https://www.svgrepo.com/show/305384/arrowhead-left-outline.svg" + } } ] }, { "category": "VERIFICATION", - "actions": [ + "display": { + "label": "Verification", + "image": "" + }, + "types": [ { "type": "NEXT", "meta": { "actionType": "VERIFICATION" }, - "displayName": "Verify email address" + "display": { + "label": "Verify email address", + "image": "https://www.svgrepo.com/show/408695/message-letter-mail-tick-check-e-mail.svg" + } }, { "type": "NEXT", "meta": { "actionType": "VERIFICATION" }, - "displayName": "Verify mobile number" + "display": { + "label": "Verify mobile number", + "image": "https://www.svgrepo.com/show/533121/mobile-check.svg" + } + } + ] + }, + { + "category": "CREDENTIAL_ONBOARDING", + "display": { + "label": "Credential Onboarding", + "image": "" + }, + "types": [ + { + "type": "EXECUTOR", + "name": "PasswordOnboarder", + "display": { + "label": "Onboard Password", + "image": "https://www.svgrepo.com/show/526627/password.svg" + }, + "meta": { + "actionType": "CREDENTIAL_ONBOARDING" + } } ] }, { "category": "SOCIAL", - "actions": [ + "display": { + "label": "Social", + "image": "" + }, + "types": [ { "type": "EXECUTOR", "name": "GoogleOIDCAuthenticator", "meta": { "idp": "Google" }, - "displayName": "Continue with Google Sign Up" + "display": { + "label": "Continue with Google Sign Up", + "image": "https://www.svgrepo.com/show/475656/google-color.svg" + } } ] } diff --git a/features/admin.flow-builder-core.v1/data/components.json b/features/admin.flow-builder-core.v1/data/components.json index 1a71ddfc4bf..a97acea0221 100644 --- a/features/admin.flow-builder-core.v1/data/components.json +++ b/features/admin.flow-builder-core.v1/data/components.json @@ -12,17 +12,11 @@ "config": { "field": { "type": "text", - "className": "wso2is-input text", "hint": "", "label": "Text", "required": false, - "multiline": false, - "placeholder": "Enter your text", - "defaultValue": "", - "minLength": 3, - "maxLength": 50 - }, - "styles": {} + "placeholder": "Enter your text" + } } }, { @@ -38,15 +32,11 @@ "config": { "field": { "type": "password", - "className": "wso2is-input password", "hint": "", "label": "Password", "required": false, - "multiline": false, - "placeholder": "Enter your password", - "defaultValue": "" - }, - "styles": {} + "placeholder": "Enter your password" + } } }, { @@ -62,15 +52,11 @@ "config": { "field": { "type": "email", - "className": "wso2is-input email", "hint": "", "label": "Email", "required": false, - "multiline": false, - "placeholder": "Enter your email address", - "defaultValue": "" - }, - "styles": {} + "placeholder": "Enter your email address" + } } }, { @@ -86,15 +72,11 @@ "config": { "field": { "type": "tel", - "className": "wso2is-input phone", "hint": "", "label": "Phone", "required": false, - "multiline": false, - "placeholder": "Enter your phone number", - "defaultValue": "" - }, - "styles": {} + "placeholder": "Enter your phone number" + } } }, { @@ -110,15 +92,11 @@ "config": { "field": { "type": "number", - "className": "wso2is-input number", "hint": "", "label": "Number", "required": false, - "multiline": false, - "placeholder": "Enter a number", - "defaultValue": "" - }, - "styles": {} + "placeholder": "Enter a number" + } } }, { @@ -134,15 +112,11 @@ "config": { "field": { "type": "date", - "className": "wso2is-input date", "hint": "", "label": "Date", "required": false, - "multiline": false, - "placeholder": "Enter a date", - "defaultValue": "" - }, - "styles": {} + "placeholder": "Enter a date" + } } }, { @@ -157,13 +131,10 @@ }, "config": { "field": { - "className": "wso2is-input checkbox", "hint": "", "label": "Checkbox", - "required": false, - "defaultValue": "" - }, - "styles": {} + "required": false + } } }, { @@ -177,7 +148,6 @@ }, "config": { "field": { - "className": "wso2is-input choice", "hint": "", "label": "Choice", "required": false, @@ -199,8 +169,7 @@ "value": "option3" } ] - }, - "styles": {} + } } }, { @@ -227,11 +196,7 @@ "config": { "field": { "type": "submit", - "className": "wso2is-button primary", "text": "Primary Button" - }, - "styles": { - "width": "100%" } } }, @@ -248,11 +213,7 @@ "config": { "field": { "type": "button", - "className": "wso2is-button secondary", "text": "Secondary Button" - }, - "styles": { - "width": "100%" } } }, @@ -269,11 +230,24 @@ "config": { "field": { "type": "button", - "className": "wso2is-button text", "text": "Text Button" - }, - "styles": { - "width": "100%" + } + } + }, + { + "category": "ACTION", + "type": "BUTTON", + "version": "0.1.0", + "variant": "SOCIAL", + "deprecated": false, + "display": { + "label": "Social", + "image": null + }, + "config": { + "field": { + "type": "button", + "text": "Social Button" } } } @@ -302,11 +276,7 @@ }, "config": { "field": { - "className": "wso2is-typography h1", "text": "Heading - H1" - }, - "styles": { - "textAlign": "center" } } }, @@ -322,11 +292,7 @@ }, "config": { "field": { - "className": "wso2is-typography h2", "text": "Heading - H2" - }, - "styles": { - "textAlign": "center" } } }, @@ -342,11 +308,7 @@ }, "config": { "field": { - "className": "wso2is-typography h3", "text": "Heading - H3" - }, - "styles": { - "textAlign": "center" } } }, @@ -362,11 +324,7 @@ }, "config": { "field": { - "className": "wso2is-typography h4", "text": "Heading - H4" - }, - "styles": { - "textAlign": "center" } } }, @@ -382,11 +340,7 @@ }, "config": { "field": { - "className": "wso2is-typography h5", "text": "Heading - H5" - }, - "styles": { - "textAlign": "center" } } }, @@ -402,11 +356,7 @@ }, "config": { "field": { - "className": "wso2is-typography h6", "text": "Heading - H6" - }, - "styles": { - "textAlign": "center" } } }, @@ -422,13 +372,7 @@ }, "config": { "field": { - "className": "wso2is-typography body1", "text": "Body Text" - }, - "styles": { - "fontSize": "14px", - "fontWeight": "400", - "textAlign": "center" } } }, @@ -444,13 +388,7 @@ }, "config": { "field": { - "className": "wso2is-typography body2", "text": "Body Text - Muted" - }, - "styles": { - "fontSize": "12px", - "fontWeight": "400", - "textAlign": "center" } } } @@ -467,11 +405,7 @@ }, "config": { "field": { - "className": "wso2is-rich-text", "text": "

Rich Text

" - }, - "styles": { - "textAlign": "center" } } }, @@ -498,10 +432,8 @@ }, "config": { "field": { - "className": "wso2is-divider horizontal", "text": "Or" - }, - "styles": {} + } } }, { @@ -516,10 +448,8 @@ }, "config": { "field": { - "className": "wso2is-divider vertical", "text": "Or" - }, - "styles": {} + } } } ] @@ -547,12 +477,7 @@ }, "config": { "field": { - "src": "https://www.svgrepo.com/show/508699/landscape-placeholder.svg", - "className": "wso2is-image image-block" - }, - "styles": { - "width": "inherit", - "height": "40px" + "src": "https://www.svgrepo.com/show/508699/landscape-placeholder.svg" } } } diff --git a/features/admin.flow-builder-core.v1/data/nodes.json b/features/admin.flow-builder-core.v1/data/nodes.json index 1d454458203..f565959749a 100644 --- a/features/admin.flow-builder-core.v1/data/nodes.json +++ b/features/admin.flow-builder-core.v1/data/nodes.json @@ -9,10 +9,7 @@ "image": "https://www.svgrepo.com/show/448632/step.svg" }, "config": { - "field": { - "className": "wso2is-authentication-step" - }, - "styles": {} + "field": {} } }, { @@ -25,10 +22,7 @@ "image": "https://www.svgrepo.com/show/413199/decide.svg" }, "config": { - "field": { - "className": "wso2is-authentication-rule" - }, - "styles": {} + "field": {} } } ] diff --git a/features/admin.flow-builder-core.v1/data/payload.json b/features/admin.flow-builder-core.v1/data/payload.json index 1ce60150c66..a879706a491 100644 --- a/features/admin.flow-builder-core.v1/data/payload.json +++ b/features/admin.flow-builder-core.v1/data/payload.json @@ -34,28 +34,45 @@ ], "actions": [ { - "id": "flow-action-password-onboarder-p563u9Yn", + "id": "flow-action-ty203491", "action": { - "type": "NEXT" + "type": "EXECUTOR", + "executors": [ + { + "id": "flow-action-password-onboarder-p563u9Yn", + "name": "PasswordOnboarder" + } + ] }, "next": [ "flow-node-2" ] }, { - "id": "flow-action-email-otp-verifier-5t8uJ6D3", + "id": "flow-action-394ksdj3", "action": { - "type": "NEXT" + "type": "EXECUTOR", + "executors": [ + { + "id": "flow-action-email-otp-verifier-5t8uJ6D3", + "name": "EmailOTPVerifier" + } + ] }, "next": [ "flow-node-3" ] }, { - "id": "flow-action-google-sign-up-Rt8uJ6D3", + "id": "flow-action-WeP34234", "action": { "type": "EXECUTOR", - "name": "GoogleSignUp" + "executors": [ + { + "id": "flow-action-google-sign-up-Rt8uJ6D3", + "name": "GoogleSignUp" + } + ] }, "next": [ "flow-node-5" @@ -74,8 +91,10 @@ { "id": "flow-action-next-ggh688op", "action": { - "type": "EXECUTOR", - "name": "PasswordOnboarder" + "type": "NEXT", + "meta": { + "actionType": "CREDENTIAL_ONBOARDING" + } }, "next": [ "flow-node-5" @@ -103,8 +122,10 @@ { "id": "flow-action-next-tyG3hp31", "action": { - "type": "EXECUTOR", - "name": "EmailOTPVerifier" + "type": "NEXT", + "meta": { + "actionType": "ATTRIBUTE_COLLECTION" + } }, "next": [ "flow-node-4" @@ -162,8 +183,11 @@ { "id": "flow-action-done-5t8uJ6D3", "action": { - "type": "DONE" - } + "type": "NEXT" + }, + "next": [ + "COMPLETE" + ] } ] } @@ -221,11 +245,7 @@ "variant": "H3", "config": { "field": { - "className": "wso2is-typography h3", "text": "Register Account" - }, - "styles": { - "textAlign": "center" } } }, @@ -239,17 +259,11 @@ "field": { "type": "text", "name": "username", - "className": "wso2is-input text", "hint": "", "label": "Username", "required": true, - "multiline": false, - "placeholder": "Enter your username", - "defaultValue": "", - "minLength": 3, - "maxLength": 50 - }, - "styles": {} + "placeholder": "Enter your username" + } } }, { @@ -262,17 +276,11 @@ "field": { "type": "text", "name": "firstName", - "className": "wso2is-input text", "hint": "", "label": "First Name", "required": true, - "multiline": false, - "placeholder": "Enter your first name", - "defaultValue": "", - "minLength": 3, - "maxLength": 50 - }, - "styles": {} + "placeholder": "Enter your first name" + } } }, { @@ -285,17 +293,11 @@ "field": { "type": "text", "name": "lastName", - "className": "wso2is-input text", "hint": "", "label": "Last Name", "required": true, - "multiline": false, - "placeholder": "Enter your last name", - "defaultValue": "", - "minLength": 3, - "maxLength": 50 - }, - "styles": {} + "placeholder": "Enter your last name" + } } }, { @@ -308,15 +310,11 @@ "field": { "type": "date", "name": "dob", - "className": "wso2is-input date", "hint": "", "label": "Birth Date", "required": false, - "multiline": false, - "placeholder": "Enter your birth date", - "defaultValue": "" - }, - "styles": {} + "placeholder": "Enter your birth date" + } } }, { @@ -328,11 +326,7 @@ "config": { "field": { "type": "submit", - "className": "wso2is-button", "text": "Continue with Password" - }, - "styles": { - "width": "100%" } } }, @@ -344,10 +338,8 @@ "variant": "HORIZONTAL", "config": { "field": { - "className": "wso2is-divider-horizontal", "text": "Or" - }, - "styles": {} + } } }, { @@ -359,13 +351,8 @@ "variant": "PRIMARY", "config": { "field": { - "action": "EmailOTPVerifier", "type": "submit", - "className": "wso2is-button", "label": "Continue with Email OTP" - }, - "styles": { - "width": "100%" } } }, @@ -377,10 +364,8 @@ "variant": "HORIZONTAL", "config": { "field": { - "className": "wso2is-divider-horizontal", "text": "Or" - }, - "styles": {} + } } }, { @@ -389,16 +374,11 @@ "type": "BUTTON", "version": "0.1.0", "deprecated": false, - "variant": "SOCIAL_BUTTON", + "variant": "SOCIAL", "config": { "field": { - "action": "GoogleSignUp", "type": "button", - "className": "wso2is-social-button", "label": "Continue with Google Sign Up" - }, - "styles": { - "width": "100%" } } }, @@ -410,11 +390,7 @@ "variant": "H3", "config": { "field": { - "className": "wso2is-typography h3", "text": "Enter Password" - }, - "styles": { - "textAlign": "center" } } }, @@ -428,15 +404,11 @@ "field": { "type": "password", "name": "password", - "className": "wso2is-input password", "hint": "", "label": "Password", "required": true, - "multiline": false, - "placeholder": "Enter your password", - "defaultValue": "" - }, - "styles": {} + "placeholder": "Enter your password" + } } }, { @@ -449,15 +421,11 @@ "field": { "type": "password", "name": "confirmPassword", - "className": "wso2is-input password", "hint": "", "label": "Confirm Password", "required": true, - "multiline": false, - "placeholder": "Re-enter your password", - "defaultValue": "" - }, - "styles": {} + "placeholder": "Re-enter your password" + } } }, { @@ -469,11 +437,7 @@ "config": { "field": { "type": "submit", - "className": "wso2is-button", "text": "Next" - }, - "styles": { - "width": "100%" } } }, @@ -485,11 +449,7 @@ "variant": "H3", "config": { "field": { - "className": "wso2is-typography h3", "text": "Enter Email" - }, - "styles": { - "textAlign": "center" } } }, @@ -503,15 +463,11 @@ "field": { "type": "email", "name": "email", - "className": "wso2is-input email", "hint": "", "label": "Email", "required": true, - "multiline": false, - "placeholder": "Enter your email address", - "defaultValue": "" - }, - "styles": {} + "placeholder": "Enter your email address" + } } }, { @@ -523,11 +479,7 @@ "config": { "field": { "type": "submit", - "className": "wso2is-button", "text": "Continue" - }, - "styles": { - "width": "100%" } } }, @@ -540,11 +492,7 @@ "config": { "field": { "type": "submit", - "className": "wso2is-button", "text": "Go back" - }, - "styles": { - "width": "100%" } } }, @@ -556,11 +504,7 @@ "variant": "H3", "config": { "field": { - "className": "wso2is-typography h3", "text": "OTP Verification" - }, - "styles": { - "textAlign": "center" } } }, @@ -574,15 +518,11 @@ "field": { "type": "text", "name": "otp", - "className": "wso2is-input otp", "hint": "", "label": "OTP", "required": true, - "multiline": false, - "placeholder": "Enter the OTP", - "defaultValue": "" - }, - "styles": {} + "placeholder": "Enter the OTP" + } } }, { @@ -594,11 +534,7 @@ "config": { "field": { "type": "submit", - "className": "wso2is-button", "text": "Continue" - }, - "styles": { - "width": "100%" } } }, @@ -611,11 +547,7 @@ "config": { "field": { "type": "submit", - "className": "wso2is-button", "text": "Go back" - }, - "styles": { - "width": "100%" } } }, @@ -627,11 +559,7 @@ "variant": "H3", "config": { "field": { - "className": "wso2is-typography h3", "text": "Enter Personal Details" - }, - "styles": { - "textAlign": "center" } } }, @@ -645,15 +573,11 @@ "field": { "type": "text", "name": "nic", - "className": "wso2is-text-input", "hint": "", "label": "NIC", "required": true, - "multiline": false, - "placeholder": "Enter your national identity card number", - "defaultValue": "" - }, - "styles": {} + "placeholder": "Enter your national identity card number" + } } }, { @@ -666,11 +590,7 @@ "config": { "field": { "type": "submit", - "className": "wso2is-button", "label": "Done" - }, - "styles": { - "width": "100%" } } } diff --git a/features/admin.flow-builder-core.v1/data/widgets.json b/features/admin.flow-builder-core.v1/data/widgets.json index 4c4a7df8997..25d9fc57e5c 100644 --- a/features/admin.flow-builder-core.v1/data/widgets.json +++ b/features/admin.flow-builder-core.v1/data/widgets.json @@ -10,8 +10,7 @@ }, "config": { "version": 2, - "field": {}, - "styles": {} + "field": {} } }, { @@ -24,10 +23,7 @@ "image": "https://www.svgrepo.com/show/368868/otp.svg" }, "config": { - "field": { - "className": "wso2is-email-otp" - }, - "styles": {} + "field": {} } }, { @@ -40,10 +36,7 @@ "image": "https://www.svgrepo.com/show/381137/transaction-password-otp-verification-code-security.svg" }, "config": { - "field": { - "className": "wso2is-sms-otp" - }, - "styles": {} + "field": {} } } ] From 48e8f508e15cc37281c0e5807aa61a33cd597695 Mon Sep 17 00:00:00 2001 From: Brion Date: Fri, 13 Dec 2024 13:29:23 +0530 Subject: [PATCH 05/35] Add button types --- .../element-properties.tsx | 3 +- .../data/actions.json | 18 ++++--- .../data/payload.json | 6 +-- .../element-properties.tsx | 7 ++- .../button-extended-properties.scss | 6 +++ .../button-extended-properties.tsx | 48 +++++++++++++++---- .../field-extended-properties.tsx | 2 +- 7 files changed, 70 insertions(+), 20 deletions(-) diff --git a/features/admin.flow-builder-core.v1/components/element-property-panel/element-properties.tsx b/features/admin.flow-builder-core.v1/components/element-property-panel/element-properties.tsx index 99e237a8484..7774a1c5f80 100644 --- a/features/admin.flow-builder-core.v1/components/element-property-panel/element-properties.tsx +++ b/features/admin.flow-builder-core.v1/components/element-property-panel/element-properties.tsx @@ -20,6 +20,7 @@ import Stack from "@oxygen-ui/react/Stack"; import Typography from "@oxygen-ui/react/Typography"; import { IdentifiableComponentInterface } from "@wso2is/core/models"; import { useReactFlow } from "@xyflow/react"; +import set from "lodash-es/set"; import React, { FunctionComponent, HTMLAttributes, ReactElement } from "react"; import useAuthenticationFlowBuilderCore from "../../hooks/use-authentication-flow-builder-core-context"; import { Component } from "../../models/component"; @@ -80,7 +81,7 @@ const ElementProperties: FunctionComponent = ({ updateNodeData(lastInteractedNodeId, (node: any) => { const components: Component = node?.data?.components?.map((component: any) => { if (component.id === element.id) { - component.config.field[propertyKey] = newValue; + set(component, propertyKey, newValue); } return component; diff --git a/features/admin.flow-builder-core.v1/data/actions.json b/features/admin.flow-builder-core.v1/data/actions.json index 5d1f6c3899c..701b9e73874 100644 --- a/features/admin.flow-builder-core.v1/data/actions.json +++ b/features/admin.flow-builder-core.v1/data/actions.json @@ -10,14 +10,16 @@ "type": "NEXT", "display": { "label": "Next", - "image": "https://www.svgrepo.com/show/305382/arrowhead-right-outline.svg" + "image": "https://www.svgrepo.com/show/305382/arrowhead-right-outline.svg", + "defaultVariant": "PRIMARY" } }, { "type": "PREVIOUS", "display": { "label": "Previous", - "image": "https://www.svgrepo.com/show/305384/arrowhead-left-outline.svg" + "image": "https://www.svgrepo.com/show/305384/arrowhead-left-outline.svg", + "defaultVariant": "SECONDARY" } } ] @@ -36,7 +38,8 @@ }, "display": { "label": "Verify email address", - "image": "https://www.svgrepo.com/show/408695/message-letter-mail-tick-check-e-mail.svg" + "image": "https://www.svgrepo.com/show/408695/message-letter-mail-tick-check-e-mail.svg", + "defaultVariant": "PRIMARY" } }, { @@ -46,7 +49,8 @@ }, "display": { "label": "Verify mobile number", - "image": "https://www.svgrepo.com/show/533121/mobile-check.svg" + "image": "https://www.svgrepo.com/show/533121/mobile-check.svg", + "defaultVariant": "SECONDARY" } } ] @@ -63,7 +67,8 @@ "name": "PasswordOnboarder", "display": { "label": "Onboard Password", - "image": "https://www.svgrepo.com/show/526627/password.svg" + "image": "https://www.svgrepo.com/show/526627/password.svg", + "defaultVariant": "PRIMARY" }, "meta": { "actionType": "CREDENTIAL_ONBOARDING" @@ -86,7 +91,8 @@ }, "display": { "label": "Continue with Google Sign Up", - "image": "https://www.svgrepo.com/show/475656/google-color.svg" + "image": "https://www.svgrepo.com/show/475656/google-color.svg", + "defaultVariant": "SOCIAL" } } ] diff --git a/features/admin.flow-builder-core.v1/data/payload.json b/features/admin.flow-builder-core.v1/data/payload.json index a879706a491..74b2fd5a755 100644 --- a/features/admin.flow-builder-core.v1/data/payload.json +++ b/features/admin.flow-builder-core.v1/data/payload.json @@ -275,7 +275,7 @@ "config": { "field": { "type": "text", - "name": "firstName", + "name": "http://wso2.org/claims/givenname", "hint": "", "label": "First Name", "required": true, @@ -292,7 +292,7 @@ "config": { "field": { "type": "text", - "name": "lastName", + "name": "http://wso2.org/claims/lastname", "hint": "", "label": "Last Name", "required": true, @@ -309,7 +309,7 @@ "config": { "field": { "type": "date", - "name": "dob", + "name": "http://wso2.org/claims/dob", "hint": "", "label": "Birth Date", "required": false, diff --git a/features/admin.registration-flow-builder.v1/components/element-property-panel/element-properties.tsx b/features/admin.registration-flow-builder.v1/components/element-property-panel/element-properties.tsx index baa4a16faee..36840f7919d 100644 --- a/features/admin.registration-flow-builder.v1/components/element-property-panel/element-properties.tsx +++ b/features/admin.registration-flow-builder.v1/components/element-property-panel/element-properties.tsx @@ -22,7 +22,7 @@ import { FieldKey, FieldValue, Properties } from "@wso2is/admin.flow-builder-cor import { Element, ElementCategories } from "@wso2is/admin.flow-builder-core.v1/models/elements"; import { IdentifiableComponentInterface } from "@wso2is/core/models"; import isEmpty from "lodash-es/isEmpty"; -import React, { ChangeEvent, FunctionComponent, ReactElement } from "react"; +import React, { ChangeEvent, FunctionComponent, ReactElement, useMemo } from "react"; import ElementPropertyFactory from "./element-property-factory"; import ButtonExtendedProperties from "./extended-properties/button-extended-properties"; import FieldExtendedProperties from "./extended-properties/field-extended-properties"; @@ -63,6 +63,10 @@ const ElementProperties: FunctionComponent = ({ onChange, onVariantChange }: ElementPropertiesPropsInterface): ReactElement | null => { + const selectedVariant: Element = useMemo(() => { + return element?.variants?.find((_element: Element) => _element.variant === element.variant); + }, [ element.variants, element.variant ]); + const renderElementPropertyFactory = () => { const hasVariants: boolean = !isEmpty(element?.variants); @@ -77,6 +81,7 @@ const ElementProperties: FunctionComponent = ({ renderInput={ (params: AutocompleteRenderInputParams) => ( ) } + value={ selectedVariant } onChange={ (_: ChangeEvent, variant: Element) => { onVariantChange(variant?.variant); } } diff --git a/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/button-extended-properties.scss b/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/button-extended-properties.scss index a7caad0f350..4cf60854ddf 100644 --- a/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/button-extended-properties.scss +++ b/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/button-extended-properties.scss @@ -1,5 +1,7 @@ .button-extended-properties { .extended-property.action-type { + cursor: pointer; + &.oxygen-card { // TODO: `@oxygen-ui/react/Card` declares a default padding which is a bug. // Remove this once it is handled. @@ -32,6 +34,10 @@ width: 18px; } } + + &.selected { + border: 1px solid var(--oxygen-palette-primary-main); + } } .button-extended-properties-sub-heading { diff --git a/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/button-extended-properties.tsx b/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/button-extended-properties.tsx index 435e1a9c28c..9dd264405ce 100644 --- a/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/button-extended-properties.tsx +++ b/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/button-extended-properties.tsx @@ -23,11 +23,16 @@ import CardContent from "@oxygen-ui/react/CardContent"; import Grid from "@oxygen-ui/react/Grid"; import Stack from "@oxygen-ui/react/Stack"; import Typography from "@oxygen-ui/react/Typography"; +import { + CommonComponentPropertyFactoryPropsInterface +} from "@wso2is/admin.flow-builder-core.v1/components/element-property-panel/common-component-property-factory"; // eslint-disable-next-line max-len -import { CommonComponentPropertyFactoryPropsInterface } from "@wso2is/admin.flow-builder-core.v1/components/element-property-panel/common-component-property-factory"; +import useAuthenticationFlowBuilderCore from "@wso2is/admin.flow-builder-core.v1/hooks/use-authentication-flow-builder-core-context"; import { Action, ActionType } from "@wso2is/admin.flow-builder-core.v1/models/actions"; import { IdentifiableComponentInterface } from "@wso2is/core/models"; -import React, { FunctionComponent, ReactElement } from "react"; +import classNames from "classnames"; +import isEqual from "lodash-es/isEqual"; +import React, { FunctionComponent, ReactElement, useState } from "react"; import useGetRegistrationFlowCoreActions from "../../../api/use-get-registration-flow-builder-actions"; import "./button-extended-properties.scss"; @@ -44,9 +49,14 @@ export type ButtonExtendedPropertiesPropsInterface = CommonComponentPropertyFact * @returns The ButtonExtendedProperties component. */ const ButtonExtendedProperties: FunctionComponent = ({ - "data-componentid": componentId = "button-extended-properties" + "data-componentid": componentId = "button-extended-properties", + element, + onChange }: ButtonExtendedPropertiesPropsInterface): ReactElement => { const { data: actions } = useGetRegistrationFlowCoreActions(); + const { lastInteractedElement, setLastInteractedElement } = useAuthenticationFlowBuilderCore(); + + const [ selectedActionType, setSelectedActionType ] = useState(actions[0]?.types[0]); return ( @@ -58,18 +68,40 @@ const ButtonExtendedProperties: FunctionComponent - { action.types?.map((type: ActionType, typeIndex: number) => ( - - + { action.types?.map((actionType: ActionType, typeIndex: number) => ( + { + onChange( + "variant", + selectedActionType?.display?.defaultVariant, + actionType?.display?.defaultVariant, + element + ); + + setSelectedActionType(actionType); + setLastInteractedElement({ + ...lastInteractedElement, + variant: actionType?.display?.defaultVariant + }); + } } + > + - { type?.display?.label } + { actionType?.display?.label } diff --git a/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/field-extended-properties.tsx b/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/field-extended-properties.tsx index c407572da2d..29d2de25efd 100644 --- a/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/field-extended-properties.tsx +++ b/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/field-extended-properties.tsx @@ -56,7 +56,7 @@ const FieldExtendedProperties: FunctionComponent } onChange={ (_: ChangeEvent, attribute: Attribute) => { - onChange("name", selectedAttribute?.claimURI, attribute?.claimURI, element); + onChange("config.field.name", selectedAttribute?.claimURI, attribute?.claimURI, element); setSelectedAttribute( attributes?.find((attribute: Attribute) => attribute?.claimURI === attribute?.claimURI) From 01a93fcbc618623a59ebce58e3d750472205776b Mon Sep 17 00:00:00 2001 From: Brion Date: Fri, 13 Dec 2024 14:00:00 +0530 Subject: [PATCH 06/35] Fix Attribute property selection issue --- .../common-component-property-factory.tsx | 6 +++--- .../components/adapters/input/default-input-adapter.tsx | 3 +++ .../adapters/input/phone-number-input-adapter.tsx | 3 +++ .../extended-properties/field-extended-properties.tsx | 9 ++++++++- 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/features/admin.flow-builder-core.v1/components/element-property-panel/common-component-property-factory.tsx b/features/admin.flow-builder-core.v1/components/element-property-panel/common-component-property-factory.tsx index 1875d811fe5..bdca3efd081 100644 --- a/features/admin.flow-builder-core.v1/components/element-property-panel/common-component-property-factory.tsx +++ b/features/admin.flow-builder-core.v1/components/element-property-panel/common-component-property-factory.tsx @@ -74,10 +74,10 @@ const CommonComponentPropertyFactory: FunctionComponent } + control={ } label={ startCase(propertyKey) } onChange={ (e: ChangeEvent) => - onChange(propertyKey, propertyValue, e.target.checked, element) + onChange(`config.field.${propertyKey}`, propertyValue, e.target.checked, element) } data-componentid={ `${componentId}-${propertyKey}` } /> @@ -91,7 +91,7 @@ const CommonComponentPropertyFactory: FunctionComponent) => - onChange(propertyKey, propertyValue, e.target.value, element) + onChange(`config.field.${propertyKey}`, propertyValue, e.target.value, element) } data-componentid={ `${componentId}-${propertyKey}` } /> diff --git a/features/admin.flow-builder-core.v1/components/elements/components/adapters/input/default-input-adapter.tsx b/features/admin.flow-builder-core.v1/components/elements/components/adapters/input/default-input-adapter.tsx index b157fe8079c..a993611cd90 100644 --- a/features/admin.flow-builder-core.v1/components/elements/components/adapters/input/default-input-adapter.tsx +++ b/features/admin.flow-builder-core.v1/components/elements/components/adapters/input/default-input-adapter.tsx @@ -57,6 +57,9 @@ export const DefaultInputAdapter: FunctionComponent diff --git a/features/admin.flow-builder-core.v1/components/elements/components/adapters/input/phone-number-input-adapter.tsx b/features/admin.flow-builder-core.v1/components/elements/components/adapters/input/phone-number-input-adapter.tsx index d25e42a48b6..ab482652489 100644 --- a/features/admin.flow-builder-core.v1/components/elements/components/adapters/input/phone-number-input-adapter.tsx +++ b/features/admin.flow-builder-core.v1/components/elements/components/adapters/input/phone-number-input-adapter.tsx @@ -48,6 +48,9 @@ export const PhoneNumberInputAdapter: FunctionComponent ); diff --git a/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/field-extended-properties.tsx b/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/field-extended-properties.tsx index 29d2de25efd..baff9c55ce5 100644 --- a/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/field-extended-properties.tsx +++ b/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/field-extended-properties.tsx @@ -23,7 +23,7 @@ import { CommonComponentPropertyFactoryPropsInterface } from "@wso2is/admin.flow-builder-core.v1/components/element-property-panel/common-component-property-factory"; import { IdentifiableComponentInterface } from "@wso2is/core/models"; -import React, { ChangeEvent, FunctionComponent, ReactElement, useState } from "react"; +import React, { ChangeEvent, FunctionComponent, ReactElement, useMemo, useState } from "react"; import useGetSupportedProfileAttributes from "../../../api/use-get-supported-profile-attributes"; import { Attribute } from "../../../models/attributes"; @@ -47,6 +47,12 @@ const FieldExtendedProperties: FunctionComponent(null); + const selectedValue: Attribute = useMemo(() => { + return attributes?.find( + (attribute: Attribute) => attribute?.claimURI === element.config.field.name + ); + }, [ element.config.field.name, attributes ]); + return ( attribute.displayName } sx={ { width: 300 } } renderInput={ (params: AutocompleteRenderInputParams) => } + value={ selectedValue } onChange={ (_: ChangeEvent, attribute: Attribute) => { onChange("config.field.name", selectedAttribute?.claimURI, attribute?.claimURI, element); From 8cffdc308ea04188d990a2f0a57d9efd8d5d1291 Mon Sep 17 00:00:00 2001 From: Brion Date: Fri, 13 Dec 2024 16:37:09 +0530 Subject: [PATCH 07/35] Add `OTPInputAdapter` --- .../adapters/input/otp-input-adapter.tsx | 74 +++++++++++++++++++ .../components/common-component-factory.tsx | 5 ++ .../data/components.json | 22 +++++- .../models/component.ts | 1 + 4 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 features/admin.flow-builder-core.v1/components/elements/components/adapters/input/otp-input-adapter.tsx diff --git a/features/admin.flow-builder-core.v1/components/elements/components/adapters/input/otp-input-adapter.tsx b/features/admin.flow-builder-core.v1/components/elements/components/adapters/input/otp-input-adapter.tsx new file mode 100644 index 00000000000..19ae9c417df --- /dev/null +++ b/features/admin.flow-builder-core.v1/components/elements/components/adapters/input/otp-input-adapter.tsx @@ -0,0 +1,74 @@ +/** + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import FormHelperText from "@mui/material/FormHelperText"; +import Box from "@oxygen-ui/react/Box"; +import InputLabel from "@oxygen-ui/react/InputLabel"; +import OutlinedInput from "@oxygen-ui/react/OutlinedInput"; +import { IdentifiableComponentInterface } from "@wso2is/core/models"; +import React, { FunctionComponent, ReactElement } from "react"; +import { Component } from "../../../../../models/component"; + +/** + * Props interface of {@link OTPInputAdapter} + */ +export interface OTPInputAdapterPropsInterface extends IdentifiableComponentInterface { + /** + * The flow id of the node. + */ + nodeId: string; + /** + * The node properties. + */ + node: Component; +} + +/** + * Adapter for the OTP inputs. + * + * @param props - Props injected to the component. + * @returns The OTPInputAdapter component. + */ +export const OTPInputAdapter: FunctionComponent = ({ + node +}: OTPInputAdapterPropsInterface): ReactElement => { + return ( +
+ + { node.config?.field?.label } + + + { [ ...Array(6) ].map((_: number, index: number) => ( + + )) } + + { node.config?.field?.hint && ( + { node.config?.field?.hint } + ) } +
+ ); +}; + +export default OTPInputAdapter; diff --git a/features/admin.flow-builder-core.v1/components/elements/components/common-component-factory.tsx b/features/admin.flow-builder-core.v1/components/elements/components/common-component-factory.tsx index 6c2df9105ec..15ad6b5c8d9 100644 --- a/features/admin.flow-builder-core.v1/components/elements/components/common-component-factory.tsx +++ b/features/admin.flow-builder-core.v1/components/elements/components/common-component-factory.tsx @@ -25,6 +25,7 @@ import DividerAdapter from "./adapters/divider-adapter"; import ImageAdapter from "./adapters/image-adapter"; import CheckboxAdapter from "./adapters/input/checkbox-adapter"; import DefaultInputAdapter from "./adapters/input/default-input-adapter"; +import OTPInputAdapter from "./adapters/input/otp-input-adapter"; import PhoneNumberInputAdapter from "./adapters/input/phone-number-input-adapter"; import RichTextAdapter from "./adapters/rich-text-adapter"; import TypographyAdapter from "./adapters/typography-adapter"; @@ -63,6 +64,10 @@ export const CommonComponentFactory: FunctionComponent; } + if (node.variant === InputVariants.OTP) { + return ; + } + return ; } else if (node.type === ComponentTypes.Choice) { return ; diff --git a/features/admin.flow-builder-core.v1/data/components.json b/features/admin.flow-builder-core.v1/data/components.json index a97acea0221..2db17fa6104 100644 --- a/features/admin.flow-builder-core.v1/data/components.json +++ b/features/admin.flow-builder-core.v1/data/components.json @@ -119,6 +119,26 @@ } } }, + { + "category": "FIELD", + "type": "INPUT", + "version": "0.1.0", + "deprecated": false, + "variant": "OTP", + "display": { + "label": "OTP Input", + "image": "https://www.svgrepo.com/show/381137/transaction-password-otp-verification-code-security.svg" + }, + "config": { + "field": { + "type": "text", + "hint": "", + "label": "OTP", + "required": false, + "placeholder": "" + } + } + }, { "category": "FIELD", "type": "INPUT", @@ -196,7 +216,7 @@ "config": { "field": { "type": "submit", - "text": "Primary Button" + "text": "Button" } } }, diff --git a/features/admin.flow-builder-core.v1/models/component.ts b/features/admin.flow-builder-core.v1/models/component.ts index 8200863fa28..b45eb98e848 100644 --- a/features/admin.flow-builder-core.v1/models/component.ts +++ b/features/admin.flow-builder-core.v1/models/component.ts @@ -40,6 +40,7 @@ export enum InputVariants { Telephone = "TELEPHONE", Number = "NUMBER", Checkbox = "CHECKBOX", + OTP = "OTP" } export enum ButtonVariants { From 7ef3fd6eb618a96bb12d4d5cf6530a34c058591e Mon Sep 17 00:00:00 2001 From: Brion Date: Fri, 13 Dec 2024 18:05:33 +0530 Subject: [PATCH 08/35] Update `payload.json` --- features/admin.flow-builder-core.v1/data/payload.json | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/features/admin.flow-builder-core.v1/data/payload.json b/features/admin.flow-builder-core.v1/data/payload.json index 74b2fd5a755..a8ad69c8923 100644 --- a/features/admin.flow-builder-core.v1/data/payload.json +++ b/features/admin.flow-builder-core.v1/data/payload.json @@ -34,12 +34,11 @@ ], "actions": [ { - "id": "flow-action-ty203491", + "id": "flow-action-password-onboarder-p563u9Yn", "action": { "type": "EXECUTOR", "executors": [ { - "id": "flow-action-password-onboarder-p563u9Yn", "name": "PasswordOnboarder" } ] @@ -49,12 +48,11 @@ ] }, { - "id": "flow-action-394ksdj3", + "id": "flow-action-email-otp-verifier-5t8uJ6D3", "action": { "type": "EXECUTOR", "executors": [ { - "id": "flow-action-email-otp-verifier-5t8uJ6D3", "name": "EmailOTPVerifier" } ] @@ -64,12 +62,11 @@ ] }, { - "id": "flow-action-WeP34234", + "id": "flow-action-google-sign-up-Rt8uJ6D3", "action": { "type": "EXECUTOR", "executors": [ { - "id": "flow-action-google-sign-up-Rt8uJ6D3", "name": "GoogleSignUp" } ] @@ -442,7 +439,7 @@ } }, { - "id": "flow-display-header-hj6t4D3", + "id": "flow-display-header-rg6pwt0", "category": "DISPLAY", "type": "TYPOGRAPHY", "version": "0.1.0", From 964a0e5bb0534554312309ef6715d5ca235bef95 Mon Sep 17 00:00:00 2001 From: Brion Date: Fri, 13 Dec 2024 18:20:15 +0530 Subject: [PATCH 09/35] Add sub flow --- .../data/widgets.json | 194 ++++++++++++++++++ 1 file changed, 194 insertions(+) diff --git a/features/admin.flow-builder-core.v1/data/widgets.json b/features/admin.flow-builder-core.v1/data/widgets.json index 25d9fc57e5c..99cc10affe1 100644 --- a/features/admin.flow-builder-core.v1/data/widgets.json +++ b/features/admin.flow-builder-core.v1/data/widgets.json @@ -24,6 +24,200 @@ }, "config": { "field": {} + }, + "flow": { + "nodes": [ + { + "id": "flow-node-3", + "elements": [ + "flow-display-header-rg6pwt0", + "flow-block-attributes-53fdsfsp", + "flow-action-go-back-er212kfl" + ], + "actions": [ + { + "id": "flow-action-next-tyG3hp31", + "action": { + "type": "NEXT", + "meta": { + "actionType": "ATTRIBUTE_COLLECTION" + } + }, + "next": [ + "flow-node-4" + ] + }, + { + "id": "flow-action-go-back-er212kfl", + "action": { + "type": "PREVIOUS" + }, + "previous": [ + "flow-node-1" + ] + } + ] + }, + { + "id": "flow-node-4", + "elements": [ + "flow-display-header-GG456y7", + "flow-block-attributes-45owsew2", + "flow-action-go-back-yt5g5t" + ], + "actions": [ + { + "id": "flow-action-verify-otp-ssd5g6h", + "action": { + "type": "NEXT", + "meta": { + "actionType": "VERIFICATION" + } + }, + "next": [ + "flow-node-5" + ] + }, + { + "id": "flow-action-go-back-yt5g5t", + "action": { + "type": "PREVIOUS" + }, + "previous": [ + "flow-node-3" + ] + } + ] + } + ], + "blocks": [ + { + "id": "flow-block-attributes-53fdsfsp", + "nodes": [ + "flow-field-email-Rt8uJ6D3", + "flow-action-next-tyG3hp31" + ] + }, + { + "id": "flow-block-attributes-45owsew2", + "nodes": [ + "flow-field-otp-mn44gh0j", + "flow-action-verify-otp-ssd5g6h" + ] + } + ], + "elements": [ + { + "id": "flow-display-header-rg6pwt0", + "category": "DISPLAY", + "type": "TYPOGRAPHY", + "version": "0.1.0", + "variant": "H3", + "config": { + "field": { + "text": "Enter Email" + } + } + }, + { + "id": "flow-field-email-Rt8uJ6D3", + "category": "FIELD", + "type": "INPUT", + "version": "0.1.0", + "variant": "EMAIL", + "config": { + "field": { + "type": "email", + "name": "email", + "hint": "", + "label": "Email", + "required": true, + "placeholder": "Enter your email address" + } + } + }, + { + "id": "flow-action-next-tyG3hp31", + "category": "ACTION", + "type": "BUTTON", + "version": "0.1.0", + "variant": "PRIMARY", + "config": { + "field": { + "type": "submit", + "text": "Continue" + } + } + }, + { + "id": "flow-action-go-back-er212kfl", + "category": "ACTION", + "type": "BUTTON", + "version": "0.1.0", + "variant": "SECONDARY", + "config": { + "field": { + "type": "submit", + "text": "Go back" + } + } + }, + { + "id": "flow-display-header-GG456y7", + "category": "DISPLAY", + "type": "TYPOGRAPHY", + "version": "0.1.0", + "variant": "H3", + "config": { + "field": { + "text": "OTP Verification" + } + } + }, + { + "id": "flow-field-otp-mn44gh0j", + "category": "FIELD", + "type": "INPUT", + "version": "0.1.0", + "variant": "OTP", + "config": { + "field": { + "type": "text", + "name": "otp", + "hint": "", + "label": "OTP", + "required": true, + "placeholder": "Enter the OTP" + } + } + }, + { + "id": "flow-action-verify-otp-ssd5g6h", + "category": "ACTION", + "type": "BUTTON", + "version": "0.1.0", + "variant": "PRIMARY", + "config": { + "field": { + "type": "submit", + "text": "Continue" + } + } + }, + { + "id": "flow-action-go-back-yt5g5t", + "category": "ACTION", + "type": "BUTTON", + "version": "0.1.0", + "variant": "SECONDARY", + "config": { + "field": { + "type": "submit", + "text": "Go back" + } + } + } + ] } }, { From 5229fe72eb9796b206dbf113ac696eb9f660eb14 Mon Sep 17 00:00:00 2001 From: Brion Date: Mon, 16 Dec 2024 13:32:05 +0530 Subject: [PATCH 10/35] Add button handles --- .../elements/components/adapters/button-adapter.tsx | 11 ++++++++--- .../components/elements/nodes/step/step.scss | 4 ++++ .../components/elements/nodes/step/step.tsx | 1 - .../components/visual-flow.scss | 5 +++++ modules/dnd/src/components/droppable-container.scss | 2 +- 5 files changed, 18 insertions(+), 5 deletions(-) diff --git a/features/admin.flow-builder-core.v1/components/elements/components/adapters/button-adapter.tsx b/features/admin.flow-builder-core.v1/components/elements/components/adapters/button-adapter.tsx index 0da7b334cd9..6ca02c0bad4 100644 --- a/features/admin.flow-builder-core.v1/components/elements/components/adapters/button-adapter.tsx +++ b/features/admin.flow-builder-core.v1/components/elements/components/adapters/button-adapter.tsx @@ -18,6 +18,7 @@ import Button, { ButtonProps } from "@oxygen-ui/react/Button"; import { IdentifiableComponentInterface } from "@wso2is/core/models"; +import { Handle, Position } from "@xyflow/react"; import React, { FunctionComponent, ReactElement } from "react"; import { ButtonVariants, Component } from "../../../../models/component"; import "./button-adapter.scss"; @@ -77,9 +78,13 @@ export const ButtonAdapter: FunctionComponent = ({ } return ( - +
+ + + +
); }; diff --git a/features/admin.flow-builder-core.v1/components/elements/nodes/step/step.scss b/features/admin.flow-builder-core.v1/components/elements/nodes/step/step.scss index 9445bad46ae..67fa6152242 100644 --- a/features/admin.flow-builder-core.v1/components/elements/nodes/step/step.scss +++ b/features/admin.flow-builder-core.v1/components/elements/nodes/step/step.scss @@ -64,6 +64,10 @@ .flow-builder-step-content-form-field-content { width: 100%; + + .adapter { + position: relative; + } } &:hover { diff --git a/features/admin.flow-builder-core.v1/components/elements/nodes/step/step.tsx b/features/admin.flow-builder-core.v1/components/elements/nodes/step/step.tsx index 41376103963..dfe913bb25c 100644 --- a/features/admin.flow-builder-core.v1/components/elements/nodes/step/step.tsx +++ b/features/admin.flow-builder-core.v1/components/elements/nodes/step/step.tsx @@ -209,7 +209,6 @@ export const Step: FunctionComponent = ({ -
); }; diff --git a/features/admin.flow-builder-core.v1/components/visual-flow.scss b/features/admin.flow-builder-core.v1/components/visual-flow.scss index 6e4c525fcb1..5151a53316b 100644 --- a/features/admin.flow-builder-core.v1/components/visual-flow.scss +++ b/features/admin.flow-builder-core.v1/components/visual-flow.scss @@ -35,6 +35,10 @@ } .react-flow { + .react-flow__edges { + z-index: 1; + } + .react-flow__edge { &:hover { .react-flow__edge-path { @@ -51,6 +55,7 @@ height: var(--xy-handle-height); width: var(--xy-handle-width); border-width: var(--xy-handle-border-width-default); + z-index: 1; // TODO: Fix offset issues. // &.react-flow__handle-left, &.react-flow__handle-right { diff --git a/modules/dnd/src/components/droppable-container.scss b/modules/dnd/src/components/droppable-container.scss index 8b9cff1a0bc..207a6c1b029 100644 --- a/modules/dnd/src/components/droppable-container.scss +++ b/modules/dnd/src/components/droppable-container.scss @@ -28,7 +28,7 @@ &:active { background: #f9f9f9; cursor: grabbing; - transform: scale(1.03); + // transform: scale(1.03); } } } From 47a9891704ba39392b07582c633d344a1dca6fe6 Mon Sep 17 00:00:00 2001 From: Brion Date: Mon, 16 Dec 2024 13:32:22 +0530 Subject: [PATCH 11/35] Update data --- features/admin.flow-builder-core.v1/data/payload.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/features/admin.flow-builder-core.v1/data/payload.json b/features/admin.flow-builder-core.v1/data/payload.json index a8ad69c8923..d7631658dd3 100644 --- a/features/admin.flow-builder-core.v1/data/payload.json +++ b/features/admin.flow-builder-core.v1/data/payload.json @@ -67,7 +67,10 @@ "type": "EXECUTOR", "executors": [ { - "name": "GoogleSignUp" + "name": "GoogleOIDCAuthenticator", + "meta": { + "idp": "Google" + } } ] }, From 73b97ebd0d843d49062465cb76d410bf6051019b Mon Sep 17 00:00:00 2001 From: Brion Date: Mon, 16 Dec 2024 14:55:43 +0530 Subject: [PATCH 12/35] Implement initial transformer --- .../components/visual-flow.tsx | 4 +- .../utils/transform-flow.ts | 67 +++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 features/admin.flow-builder-core.v1/utils/transform-flow.ts diff --git a/features/admin.flow-builder-core.v1/components/visual-flow.tsx b/features/admin.flow-builder-core.v1/components/visual-flow.tsx index c3ac92f2f09..b293800265b 100644 --- a/features/admin.flow-builder-core.v1/components/visual-flow.tsx +++ b/features/admin.flow-builder-core.v1/components/visual-flow.tsx @@ -44,6 +44,7 @@ import NodeFactory from "./elements/nodes/node-factory"; import useAuthenticationFlowBuilderCore from "../hooks/use-authentication-flow-builder-core-context"; import { ElementCategories, Elements } from "../models/elements"; import { Node } from "../models/node"; +import transformFlow from "../utils/transform-flow"; import "@xyflow/react/dist/style.css"; import "./visual-flow.scss"; @@ -144,7 +145,8 @@ const VisualFlow: FunctionComponent = ({ const handlePublish = (): void => { const flow: any = toObject(); - console.log(JSON.stringify(flow, null, 2)); + console.log('Raw', JSON.stringify(flow, null, 2)); + console.log('Transformed', JSON.stringify(transformFlow(flow), null, 2)); }; const generateNodeTypes = () => { diff --git a/features/admin.flow-builder-core.v1/utils/transform-flow.ts b/features/admin.flow-builder-core.v1/utils/transform-flow.ts new file mode 100644 index 00000000000..a9db2749e7f --- /dev/null +++ b/features/admin.flow-builder-core.v1/utils/transform-flow.ts @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Node as XYFlowNode } from "@xyflow/react"; +import omit from "lodash-es/omit"; +import { Payload } from "../models/api"; +import { Element } from "../models/elements"; +import { NodeData } from "../models/node"; + +const DISPLAY_ONLY_ELEMENT_PROPERTIES: string[] = [ "display", "version", "variants" ]; + +const transformFlow = (flowState: any): Payload => { + const output: Payload = { + blocks: [ + { + id: "flow-block-1", + nodes: flowState.nodes[0].data.components.map((component: Element) => component.id) + } + ], + elements: flowState.nodes[0].data.components.map((component: Element) => + omit(component, DISPLAY_ONLY_ELEMENT_PROPERTIES) + ), + flow: { + pages: [ + { + id: "flow-page-1", + nodes: [ flowState.nodes[0].id ] + } + ] + }, + nodes: flowState.nodes.map((node: XYFlowNode) => ({ + actions: node.data.components + .filter((component: Element) => component.category === "ACTION") + .map((action: Element) => ({ + action: { + meta: { + actionType: "ATTRIBUTE_COLLECTION" + }, + type: "NEXT" + }, + id: action.id, + next: [ "COMPLETE" ] + })), + elements: node.data.components.map((component: Element) => component.id), + id: node.id + })) + }; + + return output; +}; + +export default transformFlow; From 0342ae22b5f14dc43208fa2e48d37cf37b3de347 Mon Sep 17 00:00:00 2001 From: Brion Date: Mon, 16 Dec 2024 16:43:36 +0530 Subject: [PATCH 13/35] Improve the transformer --- .../data/actions.json | 8 ++- .../data/widgets.json | 12 ++-- .../models/actions.ts | 7 ++ .../admin.flow-builder-core.v1/models/api.ts | 64 +++++++++++++++++++ .../admin.flow-builder-core.v1/models/base.ts | 4 ++ .../admin.flow-builder-core.v1/models/node.ts | 6 ++ ...hentication-flow-builder-core-provider.tsx | 9 ++- .../utils/transform-flow.ts | 54 ++++++++++------ .../button-extended-properties.tsx | 23 +++++++ 9 files changed, 153 insertions(+), 34 deletions(-) create mode 100644 features/admin.flow-builder-core.v1/models/api.ts diff --git a/features/admin.flow-builder-core.v1/data/actions.json b/features/admin.flow-builder-core.v1/data/actions.json index 701b9e73874..e8ccff816fe 100644 --- a/features/admin.flow-builder-core.v1/data/actions.json +++ b/features/admin.flow-builder-core.v1/data/actions.json @@ -65,13 +65,15 @@ { "type": "EXECUTOR", "name": "PasswordOnboarder", + "executors": [ + { + "name": "PasswordOnboarder" + } + ], "display": { "label": "Onboard Password", "image": "https://www.svgrepo.com/show/526627/password.svg", "defaultVariant": "PRIMARY" - }, - "meta": { - "actionType": "CREDENTIAL_ONBOARDING" } } ] diff --git a/features/admin.flow-builder-core.v1/data/widgets.json b/features/admin.flow-builder-core.v1/data/widgets.json index 99cc10affe1..288fa6f872e 100644 --- a/features/admin.flow-builder-core.v1/data/widgets.json +++ b/features/admin.flow-builder-core.v1/data/widgets.json @@ -28,7 +28,7 @@ "flow": { "nodes": [ { - "id": "flow-node-3", + "id": "flow-node-{{STEP_NUMBER}}", "elements": [ "flow-display-header-rg6pwt0", "flow-block-attributes-53fdsfsp", @@ -44,7 +44,7 @@ } }, "next": [ - "flow-node-4" + "{{NEXT_STEP_ID}}" ] }, { @@ -53,13 +53,13 @@ "type": "PREVIOUS" }, "previous": [ - "flow-node-1" + "{{PREVIOUS_STEP_ID}}" ] } ] }, { - "id": "flow-node-4", + "id": "flow-node-{{STEP_NUMBER}}", "elements": [ "flow-display-header-GG456y7", "flow-block-attributes-45owsew2", @@ -75,7 +75,7 @@ } }, "next": [ - "flow-node-5" + "{{NEXT_STEP_ID}}" ] }, { @@ -84,7 +84,7 @@ "type": "PREVIOUS" }, "previous": [ - "flow-node-3" + "{{PREVIOUS_STEP_ID}}" ] } ] diff --git a/features/admin.flow-builder-core.v1/models/actions.ts b/features/admin.flow-builder-core.v1/models/actions.ts index d1d2a9bbb21..a08e33db476 100644 --- a/features/admin.flow-builder-core.v1/models/actions.ts +++ b/features/admin.flow-builder-core.v1/models/actions.ts @@ -24,9 +24,16 @@ export interface Action { types: ActionType[]; } +export interface Executor { + name: string; + meta: Record; +} + export interface ActionType { type: ActionTypes; display: BaseDisplay; + name?: string; + executors: Executor[]; meta?: Record; } diff --git a/features/admin.flow-builder-core.v1/models/api.ts b/features/admin.flow-builder-core.v1/models/api.ts new file mode 100644 index 00000000000..638c203d24a --- /dev/null +++ b/features/admin.flow-builder-core.v1/models/api.ts @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ActionTypes } from "./actions"; +import { Element } from "./elements"; + +export interface Page { + id: string; + nodes: string[]; +} + +export interface Flow { + pages: Page[]; +} + +export interface ExecutorInfo { + name: string; + meta: Record; +} + +export interface ActionInfo { + type: ActionTypes; + executors: ExecutorInfo[] +} + +export interface Action { + id: string; + action: ActionInfo; + next: string[]; +} + +export interface Node { + id: string; + elements: string[]; + actions: Action; + data: Record; +} + +export interface Block { + id: string; + nodes: string[]; +} + +export interface Payload { + flow: Flow; + nodes: Node; + blocks: Block[]; + elements: Omit[]; +} diff --git a/features/admin.flow-builder-core.v1/models/base.ts b/features/admin.flow-builder-core.v1/models/base.ts index 66c9c55080d..a004e1383a6 100644 --- a/features/admin.flow-builder-core.v1/models/base.ts +++ b/features/admin.flow-builder-core.v1/models/base.ts @@ -69,6 +69,10 @@ export interface Base extends StrictBase { * Data added to the component by the flow builder. */ data?: any; + /** + * Addtional meta data of the component or the primitive + */ + meta?: any; } export interface BaseDisplay { diff --git a/features/admin.flow-builder-core.v1/models/node.ts b/features/admin.flow-builder-core.v1/models/node.ts index 99a2cb3b583..acb66b4b877 100644 --- a/features/admin.flow-builder-core.v1/models/node.ts +++ b/features/admin.flow-builder-core.v1/models/node.ts @@ -17,6 +17,7 @@ */ import { Base } from "./base"; +import { Element } from "./elements"; /** * Interface for a Node. @@ -27,3 +28,8 @@ export enum NodeTypes { Step = "STEP", Rule = "RULE" } + +export interface NodeData { + components: Element[]; + [key: string]: any; +} diff --git a/features/admin.flow-builder-core.v1/providers/authentication-flow-builder-core-provider.tsx b/features/admin.flow-builder-core.v1/providers/authentication-flow-builder-core-provider.tsx index 7ca2321f030..9152ff7179f 100644 --- a/features/admin.flow-builder-core.v1/providers/authentication-flow-builder-core-provider.tsx +++ b/features/admin.flow-builder-core.v1/providers/authentication-flow-builder-core-provider.tsx @@ -23,8 +23,7 @@ import { Claim } from "@wso2is/core/models"; import capitalize from "lodash-es/capitalize"; import React, { FunctionComponent, PropsWithChildren, ReactElement, ReactNode, useState } from "react"; import AuthenticationFlowBuilderCoreContext from "../context/authentication-flow-builder-core-context"; -import { Base } from "../models/base"; -import { ElementCategories } from "../models/elements"; +import { Element, ElementCategories } from "../models/elements"; import { NodeTypes } from "../models/node"; /** @@ -55,16 +54,16 @@ const AuthenticationFlowBuilderCoreProvider = ({ const [ isElementPanelOpen, setIsElementPanelOpen ] = useState(true); const [ isElementPropertiesPanelOpen, setIsOpenElementPropertiesPanel ] = useState(false); const [ elementPropertiesPanelHeading, setElementPropertiesPanelHeading ] = useState(null); - const [ lastInteractedElementInternal, setLastInteractedElementInternal ] = useState(null); + const [ lastInteractedElementInternal, setLastInteractedElementInternal ] = useState(null); const [ lastInteractedNodeId, setLastInteractedNodeId ] = useState(""); const [ selectedAttributes, setSelectedAttributes ] = useState<{ [key: string]: Claim[] }>({}); - const onElementDropOnCanvas = (element: Base, nodeId: string): void => { + const onElementDropOnCanvas = (element: Element, nodeId: string): void => { setLastInteractedElement(element); setLastInteractedNodeId(nodeId); }; - const setLastInteractedElement = (element: Base): void => { + const setLastInteractedElement = (element: Element): void => { // TODO: Internationalize this string and get from a mapping. setElementPropertiesPanelHeading( diff --git a/features/admin.flow-builder-core.v1/utils/transform-flow.ts b/features/admin.flow-builder-core.v1/utils/transform-flow.ts index a9db2749e7f..98aec4f6bd3 100644 --- a/features/admin.flow-builder-core.v1/utils/transform-flow.ts +++ b/features/admin.flow-builder-core.v1/utils/transform-flow.ts @@ -25,16 +25,8 @@ import { NodeData } from "../models/node"; const DISPLAY_ONLY_ELEMENT_PROPERTIES: string[] = [ "display", "version", "variants" ]; const transformFlow = (flowState: any): Payload => { + /* eslint-disable sort-keys */ const output: Payload = { - blocks: [ - { - id: "flow-block-1", - nodes: flowState.nodes[0].data.components.map((component: Element) => component.id) - } - ], - elements: flowState.nodes[0].data.components.map((component: Element) => - omit(component, DISPLAY_ONLY_ELEMENT_PROPERTIES) - ), flow: { pages: [ { @@ -46,20 +38,42 @@ const transformFlow = (flowState: any): Payload => { nodes: flowState.nodes.map((node: XYFlowNode) => ({ actions: node.data.components .filter((component: Element) => component.category === "ACTION") - .map((action: Element) => ({ - action: { - meta: { - actionType: "ATTRIBUTE_COLLECTION" - }, - type: "NEXT" - }, - id: action.id, - next: [ "COMPLETE" ] - })), + .map((action: Element) => { + let _action: any = { + id: action.id, + ...action.meta + }; + + if (!action.meta) { + if (action?.config?.field?.type === "submit") { + _action = { + ..._action, + action: { + ..._action.action, + meta: { + actionType: "ATTRIBUTE_COLLECTION" + } + } + }; + } + } + + return _action; + }), elements: node.data.components.map((component: Element) => component.id), id: node.id - })) + })), + blocks: [ + { + id: "flow-block-1", + nodes: flowState.nodes[0].data.components.map((component: Element) => component.id) + } + ], + elements: flowState.nodes[0].data.components.map((component: Element) => + omit(component, DISPLAY_ONLY_ELEMENT_PROPERTIES) + ) }; + /* eslint-enable sort-keys */ return output; }; diff --git a/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/button-extended-properties.tsx b/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/button-extended-properties.tsx index 9dd264405ce..6b44d1627e2 100644 --- a/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/button-extended-properties.tsx +++ b/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/button-extended-properties.tsx @@ -80,9 +80,32 @@ const ButtonExtendedProperties: FunctionComponent Date: Tue, 17 Dec 2024 11:04:17 +0530 Subject: [PATCH 14/35] Initial transform logic --- .../utils/transform-flow.ts | 80 +++++++++++-------- 1 file changed, 47 insertions(+), 33 deletions(-) diff --git a/features/admin.flow-builder-core.v1/utils/transform-flow.ts b/features/admin.flow-builder-core.v1/utils/transform-flow.ts index 98aec4f6bd3..1d78a369f7d 100644 --- a/features/admin.flow-builder-core.v1/utils/transform-flow.ts +++ b/features/admin.flow-builder-core.v1/utils/transform-flow.ts @@ -31,46 +31,60 @@ const transformFlow = (flowState: any): Payload => { pages: [ { id: "flow-page-1", - nodes: [ flowState.nodes[0].id ] + // Add all node IDs for the page + nodes: flowState.nodes.map((node: XYFlowNode) => node.id) } ] }, - nodes: flowState.nodes.map((node: XYFlowNode) => ({ - actions: node.data.components - .filter((component: Element) => component.category === "ACTION") - .map((action: Element) => { - let _action: any = { - id: action.id, - ...action.meta - }; + nodes: flowState.nodes.map((node: XYFlowNode) => { + // Filter FIELD elements and ACTION buttons of type "submit" + const submitElements = node.data.components.filter((component: Element) => + (component.category === "FIELD" || component.category === "ACTION") && + component.config?.field?.type === "submit" + ); + + // Handle actions separately + const actions = submitElements.map((action: Element) => { + let _action: any = { + id: action.id, + ...action.meta + }; - if (!action.meta) { - if (action?.config?.field?.type === "submit") { - _action = { - ..._action, - action: { - ..._action.action, - meta: { - actionType: "ATTRIBUTE_COLLECTION" - } - } - }; + if (!action.meta && action?.config?.field?.type === "submit") { + _action = { + ..._action, + action: { + ..._action.action, + meta: { + actionType: "ATTRIBUTE_COLLECTION" + } } - } + }; + } + + return _action; + }); - return _action; - }), - elements: node.data.components.map((component: Element) => component.id), - id: node.id + return { + actions, + elements: node.data.components.map((component: Element) => component.id), + id: node.id + }; + }), + blocks: flowState.nodes.map((node: XYFlowNode, index: number) => ({ + // For each node, create a block containing the components of type "submit" + id: `flow-block-${index + 1}`, + elements: node.data.components + .filter((component: Element) => + (component.category === "FIELD" || component.category === "ACTION") && + component.config?.field?.type === "submit" + ) + .map((component: Element) => component.id) // Collect only submit buttons and fields })), - blocks: [ - { - id: "flow-block-1", - nodes: flowState.nodes[0].data.components.map((component: Element) => component.id) - } - ], - elements: flowState.nodes[0].data.components.map((component: Element) => - omit(component, DISPLAY_ONLY_ELEMENT_PROPERTIES) + elements: flowState.nodes.flatMap((node: XYFlowNode) => + node.data.components.map((component: Element) => + omit(component, DISPLAY_ONLY_ELEMENT_PROPERTIES) + ) ) }; /* eslint-enable sort-keys */ From 305671cd94e2426acba9afc88ccd1ab0bce08cf3 Mon Sep 17 00:00:00 2001 From: Brion Date: Tue, 17 Dec 2024 11:40:07 +0530 Subject: [PATCH 15/35] Update transforming logic --- .../utils/transform-flow.ts | 89 ++++++++++--------- 1 file changed, 48 insertions(+), 41 deletions(-) diff --git a/features/admin.flow-builder-core.v1/utils/transform-flow.ts b/features/admin.flow-builder-core.v1/utils/transform-flow.ts index 1d78a369f7d..98e8c64b16b 100644 --- a/features/admin.flow-builder-core.v1/utils/transform-flow.ts +++ b/features/admin.flow-builder-core.v1/utils/transform-flow.ts @@ -18,33 +18,38 @@ import { Node as XYFlowNode } from "@xyflow/react"; import omit from "lodash-es/omit"; -import { Payload } from "../models/api"; +import { + Payload, + PayloadBlocks, + PayloadElement, + PayloadElements, + PayloadFlow, + PayloadNode, + PayloadNodes +} from "../models/api"; import { Element } from "../models/elements"; import { NodeData } from "../models/node"; const DISPLAY_ONLY_ELEMENT_PROPERTIES: string[] = [ "display", "version", "variants" ]; const transformFlow = (flowState: any): Payload => { - /* eslint-disable sort-keys */ - const output: Payload = { - flow: { - pages: [ - { - id: "flow-page-1", - // Add all node IDs for the page - nodes: flowState.nodes.map((node: XYFlowNode) => node.id) - } - ] - }, - nodes: flowState.nodes.map((node: XYFlowNode) => { - // Filter FIELD elements and ACTION buttons of type "submit" - const submitElements = node.data.components.filter((component: Element) => - (component.category === "FIELD" || component.category === "ACTION") && - component.config?.field?.type === "submit" - ); + const { nodes: flowNodes } = flowState; + + const flow: PayloadFlow = { + pages: [] + }; + const nodes: PayloadNodes = []; + const blocks: PayloadBlocks = []; + const elements: PayloadElements = []; + + flowNodes.forEach((node: XYFlowNode, index: number) => { + flow.pages.push({ + id: `flow-page-${index + 1}`, + nodes: [ node.id ] + }); - // Handle actions separately - const actions = submitElements.map((action: Element) => { + nodes.push({ + actions: node.data.components.map((action: Element) => { let _action: any = { id: action.id, ...action.meta @@ -63,33 +68,35 @@ const transformFlow = (flowState: any): Payload => { } return _action; - }); + }), + elements: node.data.components.map((component: Element) => component.id), + id: node.id + } as PayloadNode); - return { - actions, - elements: node.data.components.map((component: Element) => component.id), - id: node.id - }; - }), - blocks: flowState.nodes.map((node: XYFlowNode, index: number) => ({ - // For each node, create a block containing the components of type "submit" + blocks.push({ id: `flow-block-${index + 1}`, elements: node.data.components - .filter((component: Element) => - (component.category === "FIELD" || component.category === "ACTION") && - component.config?.field?.type === "submit" + .filter( + (component: Element) => + (component.category === "FIELD" || component.category === "ACTION") && + component.config?.field?.type === "submit" ) - .map((component: Element) => component.id) // Collect only submit buttons and fields - })), - elements: flowState.nodes.flatMap((node: XYFlowNode) => - node.data.components.map((component: Element) => + .map((component: Element) => component.id) + }); + + elements.push( + ...(node.data.components.map((component: Element) => omit(component, DISPLAY_ONLY_ELEMENT_PROPERTIES) - ) - ) - }; - /* eslint-enable sort-keys */ + ) as PayloadElements) + ); + }); - return output; + return { + flow, + nodes, + blocks, + elements + }; }; export default transformFlow; From 1f95a3447d56a64c0f35cb2f4327bdd6d56f33d9 Mon Sep 17 00:00:00 2001 From: Brion Date: Tue, 17 Dec 2024 11:42:17 +0530 Subject: [PATCH 16/35] Update transforming logic --- features/admin.flow-builder-core.v1/utils/transform-flow.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/features/admin.flow-builder-core.v1/utils/transform-flow.ts b/features/admin.flow-builder-core.v1/utils/transform-flow.ts index 98e8c64b16b..617f5d56834 100644 --- a/features/admin.flow-builder-core.v1/utils/transform-flow.ts +++ b/features/admin.flow-builder-core.v1/utils/transform-flow.ts @@ -49,7 +49,9 @@ const transformFlow = (flowState: any): Payload => { }); nodes.push({ - actions: node.data.components.map((action: Element) => { + actions: node.data.components + .filter((component: Element) => component.category === "ACTION") + .map((action: Element) => { let _action: any = { id: action.id, ...action.meta From f189dc07ed60cc4041ac02d5dba9c110b5270695 Mon Sep 17 00:00:00 2001 From: Brion Date: Tue, 17 Dec 2024 12:16:23 +0530 Subject: [PATCH 17/35] Update transforming logic --- .../utils/transform-flow.ts | 88 +++++++++++-------- 1 file changed, 52 insertions(+), 36 deletions(-) diff --git a/features/admin.flow-builder-core.v1/utils/transform-flow.ts b/features/admin.flow-builder-core.v1/utils/transform-flow.ts index 617f5d56834..81198e8e1dd 100644 --- a/features/admin.flow-builder-core.v1/utils/transform-flow.ts +++ b/features/admin.flow-builder-core.v1/utils/transform-flow.ts @@ -19,13 +19,11 @@ import { Node as XYFlowNode } from "@xyflow/react"; import omit from "lodash-es/omit"; import { - Payload, - PayloadBlocks, - PayloadElement, - PayloadElements, - PayloadFlow, - PayloadNode, - PayloadNodes + Payload as Payload, + Action as PayloadAction, + Block as PayloadBlock, + Element as PayloadElement, + Node as PayloadNode } from "../models/api"; import { Element } from "../models/elements"; import { NodeData } from "../models/node"; @@ -35,21 +33,22 @@ const DISPLAY_ONLY_ELEMENT_PROPERTIES: string[] = [ "display", "version", "varia const transformFlow = (flowState: any): Payload => { const { nodes: flowNodes } = flowState; - const flow: PayloadFlow = { - pages: [] + const payload: Payload = { + flow: { + pages: [] + }, + nodes: [], + blocks: [], + elements: [] }; - const nodes: PayloadNodes = []; - const blocks: PayloadBlocks = []; - const elements: PayloadElements = []; flowNodes.forEach((node: XYFlowNode, index: number) => { - flow.pages.push({ + payload.flow.pages.push({ id: `flow-page-${index + 1}`, nodes: [ node.id ] }); - nodes.push({ - actions: node.data.components + const nodeActions: PayloadAction[] = node.data.components .filter((component: Element) => component.category === "ACTION") .map((action: Element) => { let _action: any = { @@ -70,35 +69,52 @@ const transformFlow = (flowState: any): Payload => { } return _action; - }), - elements: node.data.components.map((component: Element) => component.id), - id: node.id - } as PayloadNode); + }); + + let currentBlock: PayloadBlock | null = null; + const nodeElements: string[] = []; + const nonBlockElements: string[] = []; - blocks.push({ - id: `flow-block-${index + 1}`, - elements: node.data.components - .filter( - (component: Element) => - (component.category === "FIELD" || component.category === "ACTION") && - component.config?.field?.type === "submit" - ) - .map((component: Element) => component.id) + // Identify the last ACTION with type "submit" + const lastSubmitActionIndex = node.data.components + .map((component: Element, index: number) => ({ component, index })) + .reverse() + .find(({ component }) => component.category === "ACTION" && component?.config?.field?.type === "submit")?.index; + + node.data.components.forEach((component: Element, index: number) => { + if (currentBlock) { + currentBlock.elements.push(component.id); + if (index === lastSubmitActionIndex) { + currentBlock = null; + } + } else { + if (component.category === "FIELD") { + currentBlock = { + id: `flow-block-${payload.blocks.length + 1}`, + elements: [ component.id ] + }; + payload.blocks.push(currentBlock); + nodeElements.push(currentBlock.id); + } else { + nodeElements.push(component.id); + } + } }); - elements.push( + payload.nodes.push({ + actions: nodeActions, + elements: nodeElements, + id: node.id + } as PayloadNode); + + payload.elements.push( ...(node.data.components.map((component: Element) => omit(component, DISPLAY_ONLY_ELEMENT_PROPERTIES) - ) as PayloadElements) + )) as PayloadElement[] ); }); - return { - flow, - nodes, - blocks, - elements - }; + return payload; }; export default transformFlow; From 83350dddc978d4b48dce8833e62ed548ac0be016 Mon Sep 17 00:00:00 2001 From: Brion Date: Tue, 17 Dec 2024 14:38:48 +0530 Subject: [PATCH 18/35] Update transforming logic --- .../utils/transform-flow.ts | 107 +++++++++++++++--- 1 file changed, 94 insertions(+), 13 deletions(-) diff --git a/features/admin.flow-builder-core.v1/utils/transform-flow.ts b/features/admin.flow-builder-core.v1/utils/transform-flow.ts index 81198e8e1dd..78078202c82 100644 --- a/features/admin.flow-builder-core.v1/utils/transform-flow.ts +++ b/features/admin.flow-builder-core.v1/utils/transform-flow.ts @@ -30,8 +30,95 @@ import { NodeData } from "../models/node"; const DISPLAY_ONLY_ELEMENT_PROPERTIES: string[] = [ "display", "version", "variants" ]; +const groupNodesIntoPages = (nodes: any[], edges: any[]): any => { + console.log("nodes", JSON.stringify(nodes)); + console.log("edges", JSON.stringify(edges)); + + const createGraphFromEdges = (edges: any[]) => { + const graph: Record = {}; // Holds the graph structure + + // Iterate through each edge and build the graph + edges.forEach((edge) => { + const { source, target } = edge; + + // Initialize the source node if not already present + if (!graph[source]) { + graph[source] = []; + } + // Initialize the target node if not already present + if (!graph[target]) { + graph[target] = []; + } + + // Add the target node to the source node's list of connections + graph[source].push(target); + // Add the source node to the target node's list of connections (undirected graph) + graph[target].push(source); + }); + + return graph; + }; + + const derivePagesFromGraph = (graph: Record) => { + const pages: any[] = []; + const visited: Set = new Set(); + let pageId = 1; + + // Helper function to perform DFS + const dfs = (node: string, path: string[]) => { + if (visited.has(node)) { + return; + } + + visited.add(node); + path.push(node); + + const neighbors = graph[node] || []; + for (const neighbor of neighbors) { + if (!visited.has(neighbor)) { + dfs(neighbor, path); + } + } + }; + + // Create a map to group nodes by their target + const targetGroups: Record = {}; + + // Iterate over all edges to create target-based groups + edges.forEach((edge) => { + const { source, target } = edge; + + // Initialize the target group if not already present + if (!targetGroups[target]) { + targetGroups[target] = []; + } + + // Add the source node to the group of its target + targetGroups[target].push(source); + }); + + // Now, create flow pages based on the target groups + Object.keys(targetGroups).forEach((target) => { + const flowPage = { + id: `flow-page-${pageId++}`, + nodes: targetGroups[target], // All sources that lead to this target + }; + + pages.push(flowPage); + }); + + return pages; + }; + + const graph = createGraphFromEdges(edges); + const pages = derivePagesFromGraph(graph); + + // Construct the final "flow" object + return pages; +}; + const transformFlow = (flowState: any): Payload => { - const { nodes: flowNodes } = flowState; + const { nodes: flowNodes, edges: flowEdges } = flowState; const payload: Payload = { flow: { @@ -43,14 +130,7 @@ const transformFlow = (flowState: any): Payload => { }; flowNodes.forEach((node: XYFlowNode, index: number) => { - payload.flow.pages.push({ - id: `flow-page-${index + 1}`, - nodes: [ node.id ] - }); - - const nodeActions: PayloadAction[] = node.data.components - .filter((component: Element) => component.category === "ACTION") - .map((action: Element) => { + const nodeActions: PayloadAction[] = node.data?.components?.filter((component: Element) => component.category === "ACTION").map((action: Element) => { let _action: any = { id: action.id, ...action.meta @@ -76,12 +156,11 @@ const transformFlow = (flowState: any): Payload => { const nonBlockElements: string[] = []; // Identify the last ACTION with type "submit" - const lastSubmitActionIndex = node.data.components - .map((component: Element, index: number) => ({ component, index })) + const lastSubmitActionIndex = node.data?.components?.map((component: Element, index: number) => ({ component, index })) .reverse() .find(({ component }) => component.category === "ACTION" && component?.config?.field?.type === "submit")?.index; - node.data.components.forEach((component: Element, index: number) => { + node.data?.components?.forEach((component: Element, index: number) => { if (currentBlock) { currentBlock.elements.push(component.id); if (index === lastSubmitActionIndex) { @@ -108,12 +187,14 @@ const transformFlow = (flowState: any): Payload => { } as PayloadNode); payload.elements.push( - ...(node.data.components.map((component: Element) => + ...(node.data?.components?.map((component: Element) => omit(component, DISPLAY_ONLY_ELEMENT_PROPERTIES) )) as PayloadElement[] ); }); + payload.flow.pages = groupNodesIntoPages(flowNodes, flowEdges); + return payload; }; From cc4cd89d7a0c88f2c13f4be3f22aaef66ecc61a8 Mon Sep 17 00:00:00 2001 From: Brion Date: Tue, 17 Dec 2024 16:39:12 +0530 Subject: [PATCH 19/35] Update transforming logic --- .../utils/transform-flow.ts | 112 ++++++++---------- 1 file changed, 50 insertions(+), 62 deletions(-) diff --git a/features/admin.flow-builder-core.v1/utils/transform-flow.ts b/features/admin.flow-builder-core.v1/utils/transform-flow.ts index 78078202c82..06304602417 100644 --- a/features/admin.flow-builder-core.v1/utils/transform-flow.ts +++ b/features/admin.flow-builder-core.v1/utils/transform-flow.ts @@ -30,90 +30,78 @@ import { NodeData } from "../models/node"; const DISPLAY_ONLY_ELEMENT_PROPERTIES: string[] = [ "display", "version", "variants" ]; -const groupNodesIntoPages = (nodes: any[], edges: any[]): any => { - console.log("nodes", JSON.stringify(nodes)); - console.log("edges", JSON.stringify(edges)); - - const createGraphFromEdges = (edges: any[]) => { - const graph: Record = {}; // Holds the graph structure - - // Iterate through each edge and build the graph +const groupNodesIntoPages = (nodes: string[], edges: any[]): any => { + // Create a directed adjacency list to represent the graph + const createGraph = (edges: any[]) => { + const graph: Record = {}; + const inDegree: Record = {}; + const outDegree: Record = {}; + + // Initialize nodes and degree tracking edges.forEach((edge) => { const { source, target } = edge; - // Initialize the source node if not already present - if (!graph[source]) { - graph[source] = []; - } - // Initialize the target node if not already present - if (!graph[target]) { - graph[target] = []; - } + if (!graph[source]) graph[source] = []; + if (!graph[target]) graph[target] = []; + if (!inDegree[source]) inDegree[source] = 0; + if (!inDegree[target]) inDegree[target] = 0; + if (!outDegree[source]) outDegree[source] = 0; + if (!outDegree[target]) outDegree[target] = 0; - // Add the target node to the source node's list of connections graph[source].push(target); - // Add the source node to the target node's list of connections (undirected graph) - graph[target].push(source); + inDegree[target]++; + outDegree[source]++; }); - return graph; + return { graph, inDegree, outDegree, edges }; }; - const derivePagesFromGraph = (graph: Record) => { + // Determine page distribution strategy based on edges + const distributeNodesToPagesWithEdgeOrder = (graphData: { + graph: Record, + inDegree: Record, + outDegree: Record, + edges: any[] + }) => { + const { graph, inDegree, edges } = graphData; const pages: any[] = []; - const visited: Set = new Set(); - let pageId = 1; - - // Helper function to perform DFS - const dfs = (node: string, path: string[]) => { - if (visited.has(node)) { - return; - } - - visited.add(node); - path.push(node); + const usedNodes = new Set(); - const neighbors = graph[node] || []; - for (const neighbor of neighbors) { - if (!visited.has(neighbor)) { - dfs(neighbor, path); - } - } - }; + // Find source nodes (nodes with no incoming edges) + const sourceNodes = Object.keys(inDegree).filter(node => inDegree[node] === 0); - // Create a map to group nodes by their target - const targetGroups: Record = {}; + // Traverse nodes and group them into pages + const traverseNodes = (nodeId: string, pageId: string) => { + if (usedNodes.has(nodeId)) return; + usedNodes.add(nodeId); - // Iterate over all edges to create target-based groups - edges.forEach((edge) => { - const { source, target } = edge; - - // Initialize the target group if not already present - if (!targetGroups[target]) { - targetGroups[target] = []; + if (!pages[pageId]) { + pages[pageId] = { + id: `flow-page-${pages.length + 1}`, + nodes: [] + }; } + pages[pageId].nodes.push(nodeId); - // Add the source node to the group of its target - targetGroups[target].push(source); - }); - - // Now, create flow pages based on the target groups - Object.keys(targetGroups).forEach((target) => { - const flowPage = { - id: `flow-page-${pageId++}`, - nodes: targetGroups[target], // All sources that lead to this target - }; + const connectedEdges = edges.filter((edge: any) => edge.source === nodeId); + connectedEdges.forEach((edge: any) => { + traverseNodes(edge.target, pageId + 1); + }); + }; - pages.push(flowPage); + sourceNodes.forEach((sourceNode) => { + traverseNodes(sourceNode, 0); }); return pages; }; - const graph = createGraphFromEdges(edges); - const pages = derivePagesFromGraph(graph); + const graphData = createGraph(edges); + const pages = distributeNodesToPagesWithEdgeOrder(graphData); + + console.log("graph", JSON.stringify(graphData.graph, null, 2)); + console.log("pages", JSON.stringify(pages, null, 2)); - // Construct the final "flow" object return pages; }; From b354fa121046c5d577376b18e7df4abd009ec772 Mon Sep 17 00:00:00 2001 From: Brion Date: Tue, 17 Dec 2024 16:54:01 +0530 Subject: [PATCH 20/35] Minor fixes --- .../element-properties.tsx | 41 +++++++++++++------ .../data/payload.json | 10 ++--- .../admin.flow-builder-core.v1/models/api.ts | 12 +++--- .../element-properties.tsx | 29 ++----------- .../button-extended-properties.tsx | 16 +++----- .../field-extended-properties.tsx | 6 +-- 6 files changed, 52 insertions(+), 62 deletions(-) diff --git a/features/admin.flow-builder-core.v1/components/element-property-panel/element-properties.tsx b/features/admin.flow-builder-core.v1/components/element-property-panel/element-properties.tsx index 7774a1c5f80..21a6783d19a 100644 --- a/features/admin.flow-builder-core.v1/components/element-property-panel/element-properties.tsx +++ b/features/admin.flow-builder-core.v1/components/element-property-panel/element-properties.tsx @@ -20,16 +20,37 @@ import Stack from "@oxygen-ui/react/Stack"; import Typography from "@oxygen-ui/react/Typography"; import { IdentifiableComponentInterface } from "@wso2is/core/models"; import { useReactFlow } from "@xyflow/react"; +import merge from "lodash-es/merge"; import set from "lodash-es/set"; -import React, { FunctionComponent, HTMLAttributes, ReactElement } from "react"; +import React, { FunctionComponent, ReactElement } from "react"; import useAuthenticationFlowBuilderCore from "../../hooks/use-authentication-flow-builder-core-context"; +import { Properties } from "../../models/base"; import { Component } from "../../models/component"; import { Element } from "../../models/elements"; /** * Props interface of {@link ElementProperties} */ -export type ElementPropertiesPropsInterface = IdentifiableComponentInterface & HTMLAttributes; +export type CommonElementPropertiesPropsInterface = IdentifiableComponentInterface & { + properties?: Properties; + /** + * The element associated with the property. + */ + element: Element; + /** + * The event handler for the property change. + * @param propertyKey - The key of the property. + * @param previousValue - The previous value of the property. + * @param newValue - The new value of the property. + * @param element - The element associated with the property. + */ + onChange: (propertyKey: string, previousValue: any, newValue: any, element: Element) => void; + /** + * The event handler for the variant change. + * @param variant - The variant of the element. + */ + onVariantChange?: (variant: string) => void; +}; /** * Component to generate the properties panel for the selected element. @@ -37,10 +58,10 @@ export type ElementPropertiesPropsInterface = IdentifiableComponentInterface & H * @param props - Props injected to the component. * @returns The ElementProperties component. */ -const ElementProperties: FunctionComponent = ({ +const ElementProperties: FunctionComponent> = ({ "data-componentid": componentId = "element-properties", ...rest -}: ElementPropertiesPropsInterface): ReactElement => { +}: Partial): ReactElement => { const { updateNodeData } = useReactFlow(); const { lastInteractedElement, @@ -57,19 +78,13 @@ const ElementProperties: FunctionComponent = ({ updateNodeData(lastInteractedNodeId, (node: any) => { const components: Component = node?.data?.components?.map((component: any) => { if (component.id === lastInteractedElement.id) { - return { - ...component, - ...selectedVariant - }; + return merge(component, selectedVariant); } return component; }); - setLastInteractedElement({ - ...lastInteractedElement, - ...selectedVariant - }); + setLastInteractedElement(merge(lastInteractedElement, selectedVariant)); return { components @@ -94,7 +109,7 @@ const ElementProperties: FunctionComponent = ({ }; return ( -
+
{ lastInteractedElement ? ( { lastInteractedElement && ( diff --git a/features/admin.flow-builder-core.v1/data/payload.json b/features/admin.flow-builder-core.v1/data/payload.json index d7631658dd3..2d3318960c4 100644 --- a/features/admin.flow-builder-core.v1/data/payload.json +++ b/features/admin.flow-builder-core.v1/data/payload.json @@ -195,7 +195,7 @@ "blocks": [ { "id": "flow-block-attributes-g55dfGuK", - "nodes": [ + "elements": [ "flow-field-email-Rt8uJ6D3", "flow-field-username-F6D3t8uJ", "flow-field-first-name-r7u4F1GG", @@ -208,21 +208,21 @@ }, { "id": "flow-block-attributes-53fdsfsp", - "nodes": [ + "elements": [ "flow-field-email-Rt8uJ6D3", "flow-action-next-tyG3hp31" ] }, { "id": "flow-block-attributes-45owsew2", - "nodes": [ + "elements": [ "flow-field-otp-mn44gh0j", "flow-action-verify-otp-ssd5g6h" ] }, { "id": "flow-block-attributes-osld3343", - "nodes": [ + "elements": [ "flow-field-password-HK8uJ903", "flow-field-confirm-password-GH63t78g", "flow-action-next-ggh688op" @@ -230,7 +230,7 @@ }, { "id": "flow-block-attributes-er203owe", - "nodes": [ + "elements": [ "flow-field-nic-RR342gr", "flow-action-done-5t8uJ6D3" ] diff --git a/features/admin.flow-builder-core.v1/models/api.ts b/features/admin.flow-builder-core.v1/models/api.ts index 638c203d24a..3e1fd205ffc 100644 --- a/features/admin.flow-builder-core.v1/models/api.ts +++ b/features/admin.flow-builder-core.v1/models/api.ts @@ -17,7 +17,7 @@ */ import { ActionTypes } from "./actions"; -import { Element } from "./elements"; +import { Element as CoreElement } from "./elements"; export interface Page { id: string; @@ -47,18 +47,20 @@ export interface Action { export interface Node { id: string; elements: string[]; - actions: Action; + actions: Action[]; data: Record; } export interface Block { id: string; - nodes: string[]; + elements: string[]; } +export type Element = Omit; + export interface Payload { flow: Flow; - nodes: Node; + nodes: Node[]; blocks: Block[]; - elements: Omit[]; + elements: Element[]; } diff --git a/features/admin.registration-flow-builder.v1/components/element-property-panel/element-properties.tsx b/features/admin.registration-flow-builder.v1/components/element-property-panel/element-properties.tsx index 36840f7919d..2c21c5eae1c 100644 --- a/features/admin.registration-flow-builder.v1/components/element-property-panel/element-properties.tsx +++ b/features/admin.registration-flow-builder.v1/components/element-property-panel/element-properties.tsx @@ -18,7 +18,8 @@ import Autocomplete, { AutocompleteRenderInputParams } from "@oxygen-ui/react/Autocomplete"; import TextField from "@oxygen-ui/react/TextField"; -import { FieldKey, FieldValue, Properties } from "@wso2is/admin.flow-builder-core.v1/models/base"; +import { CommonElementPropertiesPropsInterface } from "@wso2is/admin.flow-builder-core.v1/components/element-property-panel/element-properties"; +import { FieldKey, FieldValue } from "@wso2is/admin.flow-builder-core.v1/models/base"; import { Element, ElementCategories } from "@wso2is/admin.flow-builder-core.v1/models/elements"; import { IdentifiableComponentInterface } from "@wso2is/core/models"; import isEmpty from "lodash-es/isEmpty"; @@ -30,26 +31,7 @@ import FieldExtendedProperties from "./extended-properties/field-extended-proper /** * Props interface of {@link ElementProperties} */ -export interface ElementPropertiesPropsInterface extends IdentifiableComponentInterface { - properties: Properties; - /** - * The element associated with the property. - */ - element: Element; - /** - * The event handler for the property change. - * @param propertyKey - The key of the property. - * @param previousValue - The previous value of the property. - * @param newValue - The new value of the property. - * @param element - The element associated with the property. - */ - onChange: (propertyKey: string, previousValue: any, newValue: any, element: Element) => void; - /** - * The event handler for the variant change. - * @param variant - The variant of the element. - */ - onVariantChange: (variant: string) => void; -} +export type ElementPropertiesPropsInterface = CommonElementPropertiesPropsInterface & IdentifiableComponentInterface; /** * Factory to generate the property configurator for the given registration flow element. @@ -107,8 +89,6 @@ const ElementProperties: FunctionComponent = ({ <> @@ -120,10 +100,9 @@ const ElementProperties: FunctionComponent = ({ <> { renderElementPropertyFactory() } diff --git a/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/button-extended-properties.tsx b/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/button-extended-properties.tsx index 6b44d1627e2..932e405b0b3 100644 --- a/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/button-extended-properties.tsx +++ b/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/button-extended-properties.tsx @@ -24,8 +24,8 @@ import Grid from "@oxygen-ui/react/Grid"; import Stack from "@oxygen-ui/react/Stack"; import Typography from "@oxygen-ui/react/Typography"; import { - CommonComponentPropertyFactoryPropsInterface -} from "@wso2is/admin.flow-builder-core.v1/components/element-property-panel/common-component-property-factory"; + CommonElementPropertiesPropsInterface +} from "@wso2is/admin.flow-builder-core.v1/components/element-property-panel/element-properties"; // eslint-disable-next-line max-len import useAuthenticationFlowBuilderCore from "@wso2is/admin.flow-builder-core.v1/hooks/use-authentication-flow-builder-core-context"; import { Action, ActionType } from "@wso2is/admin.flow-builder-core.v1/models/actions"; @@ -39,7 +39,7 @@ import "./button-extended-properties.scss"; /** * Props interface of {@link ButtonExtendedProperties} */ -export type ButtonExtendedPropertiesPropsInterface = CommonComponentPropertyFactoryPropsInterface & +export type ButtonExtendedPropertiesPropsInterface = CommonElementPropertiesPropsInterface & IdentifiableComponentInterface; /** @@ -51,7 +51,8 @@ export type ButtonExtendedPropertiesPropsInterface = CommonComponentPropertyFact const ButtonExtendedProperties: FunctionComponent = ({ "data-componentid": componentId = "button-extended-properties", element, - onChange + onChange, + onVariantChange }: ButtonExtendedPropertiesPropsInterface): ReactElement => { const { data: actions } = useGetRegistrationFlowCoreActions(); const { lastInteractedElement, setLastInteractedElement } = useAuthenticationFlowBuilderCore(); @@ -73,12 +74,7 @@ const ButtonExtendedProperties: FunctionComponent { - onChange( - "variant", - selectedActionType?.display?.defaultVariant, - actionType?.display?.defaultVariant, - element - ); + onVariantChange(actionType?.display?.defaultVariant); onChange( "meta", diff --git a/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/field-extended-properties.tsx b/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/field-extended-properties.tsx index baff9c55ce5..5556e5166bd 100644 --- a/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/field-extended-properties.tsx +++ b/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/field-extended-properties.tsx @@ -19,9 +19,7 @@ import Autocomplete, { AutocompleteRenderInputParams } from "@oxygen-ui/react/Autocomplete"; import Stack from "@oxygen-ui/react/Stack"; import TextField from "@oxygen-ui/react/TextField"; -import { - CommonComponentPropertyFactoryPropsInterface -} from "@wso2is/admin.flow-builder-core.v1/components/element-property-panel/common-component-property-factory"; +import { CommonElementPropertiesPropsInterface } from "@wso2is/admin.flow-builder-core.v1/components/element-property-panel/element-properties"; import { IdentifiableComponentInterface } from "@wso2is/core/models"; import React, { ChangeEvent, FunctionComponent, ReactElement, useMemo, useState } from "react"; import useGetSupportedProfileAttributes from "../../../api/use-get-supported-profile-attributes"; @@ -30,7 +28,7 @@ import { Attribute } from "../../../models/attributes"; /** * Props interface of {@link FieldExtendedProperties} */ -export type FieldExtendedPropertiesPropsInterface = CommonComponentPropertyFactoryPropsInterface & +export type FieldExtendedPropertiesPropsInterface = CommonElementPropertiesPropsInterface & IdentifiableComponentInterface; /** From 9351c24a354414b5c5f20bbdf28879a0111c8b0b Mon Sep 17 00:00:00 2001 From: Brion Date: Wed, 18 Dec 2024 07:50:42 +0530 Subject: [PATCH 21/35] Fix action issues --- features/admin.flow-builder-core.v1/utils/transform-flow.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/features/admin.flow-builder-core.v1/utils/transform-flow.ts b/features/admin.flow-builder-core.v1/utils/transform-flow.ts index 06304602417..d1c12e29741 100644 --- a/features/admin.flow-builder-core.v1/utils/transform-flow.ts +++ b/features/admin.flow-builder-core.v1/utils/transform-flow.ts @@ -121,7 +121,7 @@ const transformFlow = (flowState: any): Payload => { const nodeActions: PayloadAction[] = node.data?.components?.filter((component: Element) => component.category === "ACTION").map((action: Element) => { let _action: any = { id: action.id, - ...action.meta + action: action.meta }; if (!action.meta && action?.config?.field?.type === "submit") { @@ -169,9 +169,9 @@ const transformFlow = (flowState: any): Payload => { }); payload.nodes.push({ - actions: nodeActions, + id: node.id, elements: nodeElements, - id: node.id + actions: nodeActions } as PayloadNode); payload.elements.push( From c98620ccaa281275b623e21a0e0315c75f73570f Mon Sep 17 00:00:00 2001 From: Brion Date: Wed, 18 Dec 2024 08:29:08 +0530 Subject: [PATCH 22/35] Update transform logic --- .../utils/transform-flow.ts | 136 +++++++----------- 1 file changed, 54 insertions(+), 82 deletions(-) diff --git a/features/admin.flow-builder-core.v1/utils/transform-flow.ts b/features/admin.flow-builder-core.v1/utils/transform-flow.ts index d1c12e29741..7a7dbe506fb 100644 --- a/features/admin.flow-builder-core.v1/utils/transform-flow.ts +++ b/features/admin.flow-builder-core.v1/utils/transform-flow.ts @@ -19,7 +19,7 @@ import { Node as XYFlowNode } from "@xyflow/react"; import omit from "lodash-es/omit"; import { - Payload as Payload, + Payload, Action as PayloadAction, Block as PayloadBlock, Element as PayloadElement, @@ -30,79 +30,40 @@ import { NodeData } from "../models/node"; const DISPLAY_ONLY_ELEMENT_PROPERTIES: string[] = [ "display", "version", "variants" ]; -const groupNodesIntoPages = (nodes: string[], edges: any[]): any => { - // Create a directed adjacency list to represent the graph - const createGraph = (edges: any[]) => { - const graph: Record = {}; - const inDegree: Record = {}; - const outDegree: Record = {}; - - // Initialize nodes and degree tracking - edges.forEach((edge) => { - const { source, target } = edge; - - if (!graph[source]) graph[source] = []; - if (!graph[target]) graph[target] = []; - if (!inDegree[source]) inDegree[source] = 0; - if (!inDegree[target]) inDegree[target] = 0; - if (!outDegree[source]) outDegree[source] = 0; - if (!outDegree[target]) outDegree[target] = 0; - - graph[source].push(target); - inDegree[target]++; - outDegree[source]++; - }); +const groupNodesIntoPages = (nodes: any[], edges: any[]): any[] => { + const nodePages: Record = {}; + const visitedNodes = new Set(); - return { graph, inDegree, outDegree, edges }; - }; + const traverseNodes = (nodeId: string, pageId: string) => { + if (visitedNodes.has(nodeId)) return; + visitedNodes.add(nodeId); - // Determine page distribution strategy based on edges - const distributeNodesToPagesWithEdgeOrder = (graphData: { - graph: Record, - inDegree: Record, - outDegree: Record, - edges: any[] - }) => { - const { graph, inDegree, edges } = graphData; - const pages: any[] = []; - const usedNodes = new Set(); - - // Find source nodes (nodes with no incoming edges) - const sourceNodes = Object.keys(inDegree).filter(node => inDegree[node] === 0); - - // Traverse nodes and group them into pages - const traverseNodes = (nodeId: string, pageId: string) => { - if (usedNodes.has(nodeId)) return; - usedNodes.add(nodeId); - - if (!pages[pageId]) { - pages[pageId] = { - id: `flow-page-${pages.length + 1}`, - nodes: [] - }; - } - pages[pageId].nodes.push(nodeId); + if (!nodePages[pageId]) { + nodePages[pageId] = []; + } + nodePages[pageId].push(nodeId); - const connectedEdges = edges.filter((edge: any) => edge.source === nodeId); - connectedEdges.forEach((edge: any) => { - traverseNodes(edge.target, pageId + 1); - }); - }; + const connectedEdges = edges.filter((edge: any) => edge.source === nodeId); - sourceNodes.forEach((sourceNode) => { - traverseNodes(sourceNode, 0); - }); + connectedEdges.forEach((edge: any) => { + const nextNodeId = edge.target; - return pages; + traverseNodes(nextNodeId, pageId + 1); + }); }; - const graphData = createGraph(edges); - const pages = distributeNodesToPagesWithEdgeOrder(graphData); + nodes.forEach((node: any) => { + if (!visitedNodes.has(node.id)) { + const pageId = `flow-page-${Object.keys(nodePages).length + 1}`; - console.log("graph", JSON.stringify(graphData.graph, null, 2)); - console.log("pages", JSON.stringify(pages, null, 2)); + traverseNodes(node.id, pageId); + } + }); - return pages; + return Object.keys(nodePages).map((pageId) => ({ + id: pageId, + nodes: nodePages[pageId] + })); }; const transformFlow = (flowState: any): Payload => { @@ -117,27 +78,38 @@ const transformFlow = (flowState: any): Payload => { elements: [] }; + const nodeIdToNextNodes: Record = {}; + + // Build a map of node IDs to their next connected nodes + flowEdges.forEach((edge: any) => { + if (!nodeIdToNextNodes[edge.source]) { + nodeIdToNextNodes[edge.source] = []; + } + nodeIdToNextNodes[edge.source].push(edge.target); + }); + flowNodes.forEach((node: XYFlowNode, index: number) => { const nodeActions: PayloadAction[] = node.data?.components?.filter((component: Element) => component.category === "ACTION").map((action: Element) => { - let _action: any = { - id: action.id, - action: action.meta - }; - - if (!action.meta && action?.config?.field?.type === "submit") { - _action = { - ..._action, - action: { - ..._action.action, - meta: { - actionType: "ATTRIBUTE_COLLECTION" - } + let _action: any = { + id: action.id, + action: action.meta, + next: nodeIdToNextNodes[node.id] || [] + }; + + if (!action.meta && action?.config?.field?.type === "submit") { + _action = { + ..._action, + action: { + ..._action.action, + meta: { + actionType: "ATTRIBUTE_COLLECTION" } - }; - } + } + }; + } - return _action; - }); + return _action; + }); let currentBlock: PayloadBlock | null = null; const nodeElements: string[] = []; From d7bc549c6c24e37776da0cc6b1697326d0ed7b2d Mon Sep 17 00:00:00 2001 From: Brion Date: Wed, 18 Dec 2024 09:23:39 +0530 Subject: [PATCH 23/35] Add complete to last node --- features/admin.flow-builder-core.v1/utils/transform-flow.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/features/admin.flow-builder-core.v1/utils/transform-flow.ts b/features/admin.flow-builder-core.v1/utils/transform-flow.ts index 7a7dbe506fb..1f3b60dd38f 100644 --- a/features/admin.flow-builder-core.v1/utils/transform-flow.ts +++ b/features/admin.flow-builder-core.v1/utils/transform-flow.ts @@ -153,6 +153,12 @@ const transformFlow = (flowState: any): Payload => { ); }); + // Add `next: ["COMPLETE"] to the last nodes' actions + const lastNode = payload.nodes[payload.nodes.length - 1]; + lastNode.actions.forEach((action: PayloadAction) => { + action.next = [ "COMPLETE" ]; + }); + payload.flow.pages = groupNodesIntoPages(flowNodes, flowEdges); return payload; From 2f44f54fa8a5e6605f1bdeb5cb63bc3fbbc6fd99 Mon Sep 17 00:00:00 2001 From: Brion Date: Wed, 18 Dec 2024 11:50:13 +0530 Subject: [PATCH 24/35] Introduce `onFlowSubmit` --- .../components/decorated-visual-flow.tsx | 9 +++++++- .../components/visual-flow.tsx | 13 +++++++++-- .../components/registration-flow-builder.tsx | 22 ++++++++++++++++--- 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/features/admin.flow-builder-core.v1/components/decorated-visual-flow.tsx b/features/admin.flow-builder-core.v1/components/decorated-visual-flow.tsx index e3bba6a1585..df0d34f4795 100644 --- a/features/admin.flow-builder-core.v1/components/decorated-visual-flow.tsx +++ b/features/admin.flow-builder-core.v1/components/decorated-visual-flow.tsx @@ -26,6 +26,7 @@ import ElementPropertiesPanel from "./element-property-panel/element-property-pa import VisualFlow from "./visual-flow"; import useAuthenticationFlowBuilderCore from "../hooks/use-authentication-flow-builder-core-context"; import { Elements } from "../models/elements"; +import { Payload } from "../models/api"; /** * Props interface of {@link DecoratedVisualFlow} @@ -37,6 +38,11 @@ export interface DecoratedVisualFlowPropsInterface * Flow elements. */ elements: Elements; + /** + * Callback to be fired when the flow is submitted. + * @param payload - Payload of the flow. + */ + onFlowSubmit: (payload: Payload) => void; } /** @@ -48,6 +54,7 @@ export interface DecoratedVisualFlowPropsInterface const DecoratedVisualFlow: FunctionComponent = ({ "data-componentid": componentId = "authentication-flow-visual-editor", elements, + onFlowSubmit, ...rest }: DecoratedVisualFlowPropsInterface): ReactElement => { const { isElementPanelOpen, isElementPropertiesPanelOpen } = useAuthenticationFlowBuilderCore(); @@ -62,7 +69,7 @@ const DecoratedVisualFlow: FunctionComponent - + diff --git a/features/admin.flow-builder-core.v1/components/visual-flow.tsx b/features/admin.flow-builder-core.v1/components/visual-flow.tsx index b293800265b..73fcffb8e63 100644 --- a/features/admin.flow-builder-core.v1/components/visual-flow.tsx +++ b/features/admin.flow-builder-core.v1/components/visual-flow.tsx @@ -42,6 +42,7 @@ import { import React, { DragEvent, FC, FunctionComponent, ReactElement, useCallback, useMemo } from "react"; import NodeFactory from "./elements/nodes/node-factory"; import useAuthenticationFlowBuilderCore from "../hooks/use-authentication-flow-builder-core-context"; +import { Payload } from "../models/api"; import { ElementCategories, Elements } from "../models/elements"; import { Node } from "../models/node"; import transformFlow from "../utils/transform-flow"; @@ -56,6 +57,11 @@ export interface VisualFlowPropsInterface extends IdentifiableComponentInterface * Flow elements. */ elements: Elements; + /** + * Callback to be fired when the flow is submitted. + * @param payload - Payload of the flow. + */ + onFlowSubmit: (payload: Payload) => void; } /** @@ -67,6 +73,7 @@ export interface VisualFlowPropsInterface extends IdentifiableComponentInterface const VisualFlow: FunctionComponent = ({ "data-componentid": componentId = "authentication-flow-visual-flow", elements, + onFlowSubmit, ...rest }: VisualFlowPropsInterface): ReactElement => { const [ nodes, setNodes, onNodesChange ] = useNodesState([]); @@ -145,8 +152,10 @@ const VisualFlow: FunctionComponent = ({ const handlePublish = (): void => { const flow: any = toObject(); - console.log('Raw', JSON.stringify(flow, null, 2)); - console.log('Transformed', JSON.stringify(transformFlow(flow), null, 2)); + console.log("Raw", JSON.stringify(flow, null, 2)); + console.log("Transformed", JSON.stringify(transformFlow(flow), null, 2)); + + onFlowSubmit(transformFlow(flow)); }; const generateNodeTypes = () => { diff --git a/features/admin.registration-flow-builder.v1/components/registration-flow-builder.tsx b/features/admin.registration-flow-builder.v1/components/registration-flow-builder.tsx index d0f62421733..c417382ffb5 100644 --- a/features/admin.registration-flow-builder.v1/components/registration-flow-builder.tsx +++ b/features/admin.registration-flow-builder.v1/components/registration-flow-builder.tsx @@ -17,12 +17,13 @@ */ import DecoratedVisualFlow from "@wso2is/admin.flow-builder-core.v1/components/decorated-visual-flow"; -import AuthenticationFlowBuilderCoreProvider from - "@wso2is/admin.flow-builder-core.v1/providers/authentication-flow-builder-core-provider"; +import { Payload } from "@wso2is/admin.flow-builder-core.v1/models/api"; +import AuthenticationFlowBuilderCoreProvider from "@wso2is/admin.flow-builder-core.v1/providers/authentication-flow-builder-core-provider"; import { IdentifiableComponentInterface } from "@wso2is/core/models"; import React, { FunctionComponent, HTMLAttributes, ReactElement } from "react"; import ElementProperties from "./element-property-panel/element-properties"; import ComponentFactory from "./elements/components/component-factory"; +import configureRegistrationFlow from "../api/configure-registration-flow"; import useGetRegistrationFlowBuilderElements from "../api/use-get-registration-flow-builder-elements"; import RegistrationFlowBuilderProvider from "../providers/registration-flow-builder-provider"; @@ -43,13 +44,28 @@ const RegistrationFlowBuilder: FunctionComponent { const { data: elements } = useGetRegistrationFlowBuilderElements(); + const handleFlowSubmit = (payload: Payload) => { + configureRegistrationFlow(payload) + .then(() => { + // Handle success. + }) + .catch(() => { + // Handle error. + }); + }; + return ( - + ); From 8d4e26d0a3fc3539a151344e8fc8a4854bf2c9ad Mon Sep 17 00:00:00 2001 From: Brion Date: Wed, 18 Dec 2024 11:50:26 +0530 Subject: [PATCH 25/35] Add `config` POST api --- .../data/actions.json | 12 +- .../api/configure-registration-flow.ts | 81 ++++++++++++ .../config/endpoints.ts | 32 +++++ .../data/payload.json | 121 ++++++++++++++++-- .../models/endpoints.ts | 28 ++++ 5 files changed, 258 insertions(+), 16 deletions(-) create mode 100644 features/admin.registration-flow-builder.v1/api/configure-registration-flow.ts create mode 100644 features/admin.registration-flow-builder.v1/config/endpoints.ts rename features/{admin.flow-builder-core.v1 => admin.registration-flow-builder.v1}/data/payload.json (73%) create mode 100644 features/admin.registration-flow-builder.v1/models/endpoints.ts diff --git a/features/admin.flow-builder-core.v1/data/actions.json b/features/admin.flow-builder-core.v1/data/actions.json index e8ccff816fe..4db4702e29c 100644 --- a/features/admin.flow-builder-core.v1/data/actions.json +++ b/features/admin.flow-builder-core.v1/data/actions.json @@ -87,10 +87,14 @@ "types": [ { "type": "EXECUTOR", - "name": "GoogleOIDCAuthenticator", - "meta": { - "idp": "Google" - }, + "executors": [ + { + "name": "GoogleOIDCAuthenticator", + "meta": { + "idp": "Google" + } + } + ], "display": { "label": "Continue with Google Sign Up", "image": "https://www.svgrepo.com/show/475656/google-color.svg", diff --git a/features/admin.registration-flow-builder.v1/api/configure-registration-flow.ts b/features/admin.registration-flow-builder.v1/api/configure-registration-flow.ts new file mode 100644 index 00000000000..60161469829 --- /dev/null +++ b/features/admin.registration-flow-builder.v1/api/configure-registration-flow.ts @@ -0,0 +1,81 @@ +/** + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { AsgardeoSPAClient, HttpClientInstance } from "@asgardeo/auth-react"; +import { RequestConfigInterface } from "@wso2is/admin.core.v1/hooks/use-request"; +import { Payload } from "@wso2is/admin.flow-builder-core.v1/models/api"; +import { HttpMethods } from "@wso2is/core/models"; +import { AxiosError, AxiosResponse } from "axios"; +import samplePayload from "../data/payload.json"; + +const httpClient: HttpClientInstance = AsgardeoSPAClient.getInstance().httpRequest.bind( + AsgardeoSPAClient.getInstance() +); + +/** + * Add a new tenant. + * + * This function calls the POST method of the following endpoint to update the tenant status. + * - `https://{serverUrl}/t/{tenantDomain}/api/server/v1/tenants` + * For more details, refer to the documentation: + * {@link https://is.docs.wso2.com/en/latest/apis/tenant-management-rest-api/#tag/Tenants/operation/addTenant} + * + * @param payload - Request payload. + * @returns A promise that resolves when the operation is complete. + * @throws Error - Throws an error if the operation fails. + */ +const configureRegistrationFlow = (payload: Payload): Promise => { + const requestConfig: RequestConfigInterface = { + data: payload, + method: HttpMethods.POST, + url: "https://localhost:9443/reg-orchestration/config" + }; + + if (payload.nodes?.length === 5) { + requestConfig.data = samplePayload; + } + + return httpClient(requestConfig) + .then((response: AxiosResponse) => { + // if (response.status !== 201) { + // throw new IdentityAppsApiException( + // TenantConstants.TENANT_CREATION_INVALID_STATUS_ERROR, + // null, + // response.status, + // response.request, + // response, + // response.config + // ); + // } + + return Promise.resolve(response.data); + }) + .catch((error: AxiosError) => { + debugger; + // throw new IdentityAppsApiException( + // TenantConstants.TENANT_CREATION_ERROR, + // error.stack, + // error.code, + // error.request, + // error.response, + // error.config + // ); + }); +}; + +export default configureRegistrationFlow; diff --git a/features/admin.registration-flow-builder.v1/config/endpoints.ts b/features/admin.registration-flow-builder.v1/config/endpoints.ts new file mode 100644 index 00000000000..2d55aba4dc2 --- /dev/null +++ b/features/admin.registration-flow-builder.v1/config/endpoints.ts @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { RegistrationFlowBuilderResourceEndpointsInterface } from "../models/endpoints"; + +/** + * Get the resource endpoints for the Registration flow builder related features. + * + * @returns Registration flow builder resource endpoints. + */ +export const getRegistrationFlowBuilderResourceEndpoints = ( + serverOrigin: string +): RegistrationFlowBuilderResourceEndpointsInterface => { + return { + configure: `${ serverOrigin }/api/asgardeo/v1/tenant/me` + }; +}; diff --git a/features/admin.flow-builder-core.v1/data/payload.json b/features/admin.registration-flow-builder.v1/data/payload.json similarity index 73% rename from features/admin.flow-builder-core.v1/data/payload.json rename to features/admin.registration-flow-builder.v1/data/payload.json index 2d3318960c4..35d5f64492d 100644 --- a/features/admin.flow-builder-core.v1/data/payload.json +++ b/features/admin.registration-flow-builder.v1/data/payload.json @@ -196,7 +196,6 @@ { "id": "flow-block-attributes-g55dfGuK", "elements": [ - "flow-field-email-Rt8uJ6D3", "flow-field-username-F6D3t8uJ", "flow-field-first-name-r7u4F1GG", "flow-field-last-name-r7u4F1GG", @@ -258,7 +257,7 @@ "config": { "field": { "type": "text", - "name": "username", + "name": "http://wso2.org/claims/username", "hint": "", "label": "Username", "required": true, @@ -311,9 +310,9 @@ "type": "date", "name": "http://wso2.org/claims/dob", "hint": "", - "label": "Birth Date", + "label": "Date of Birth", "required": false, - "placeholder": "Enter your birth date" + "placeholder": "Enter your date of birth" } } }, @@ -352,7 +351,7 @@ "config": { "field": { "type": "submit", - "label": "Continue with Email OTP" + "text": "Continue with Email OTP" } } }, @@ -378,7 +377,7 @@ "config": { "field": { "type": "button", - "label": "Continue with Google Sign Up" + "text": "Continue with Google Sign Up" } } }, @@ -407,8 +406,106 @@ "hint": "", "label": "Password", "required": true, - "placeholder": "Enter your password" - } + "multiline": false, + "placeholder": "Enter your password", + "defaultValue": "", + "validation": [ + { + "type": "CRITERIA", + "showValidationCriteria": true, + "criteria": [ + { + "label": "sign.up.form.fields.password.policies.length", + "error": "This field must be between 5 and 10 characters.", + "validation": [ + { + "type": "MIN_LENGTH", + "value": 5, + "error": "This field must be at least 5 characters." + }, + { + "type": "MAX_LENGTH", + "value": 10, + "error": "This field must be at most 10 characters." + } + ] + }, + { + "label": "sign.up.form.fields.password.policies.lowercaseAndUppercaseLetter", + "error": "This field must have at least one uppercase and lowercase letter.", + "validation": [ + { + "type": "MIN_LOWERCASE_LETTERS", + "value": 1, + "error": "Password must contain at least one lowercase letter." + }, + { + "type": "MIN_UPPERCASE_LETTERS", + "value": 1, + "error": "Password must contain at least one uppercase letter." + } + ] + }, + { + "label": "sign.up.form.fields.password.policies.minimumOneNumber", + "error": "This field must have at least one number.", + "validation": [ + { + "type": "MIN_NUMBERS", + "value": 1, + "error": "Password must contain at least one number." + } + ] + }, + { + "label": "sign.up.form.fields.password.policies.minimumOneSpecialCharacter", + "error": "This field must have at least one special character.", + "validation": [ + { + "type": "MIN_SPECIAL_CHARACTERS", + "value": 1, + "error": "Password must contain at least one special character." + } + ] + }, + { + "label": "sign.up.form.fields.password.policies.minimumOneSpecialCharacter", + "error": "This field must have at least one special character.", + "validation": [ + { + "type": "MIN_SPECIAL_CHARACTERS", + "value": 1, + "error": "Password must contain at least one special character." + } + ] + }, + { + "label": "sign.up.form.fields.password.policies.minimumOneUniqueCharacter", + "error": "This field must have at least one unique character.", + "validation": [ + { + "type": "MIN_UNIQUE_CHARACTERS", + "value": 1, + "error": "Password must contain at least one unique character." + } + ] + }, + { + "label": "sign.up.form.fields.password.policies.noRepeatedCharacters", + "error": "This field must not have repeated characters.", + "validation": [ + { + "type": "MAX_REPEATED_CHARACTERS", + "value": 0, + "error": "Password must not contain repeated characters." + } + ] + } + ] + } + ] + }, + "styles": {} } }, { @@ -462,7 +559,7 @@ "config": { "field": { "type": "email", - "name": "email", + "name": "http://wso2.org/claims/emailaddress", "hint": "", "label": "Email", "required": true, @@ -517,7 +614,7 @@ "config": { "field": { "type": "text", - "name": "otp", + "name": "email-otp", "hint": "", "label": "OTP", "required": true, @@ -572,7 +669,7 @@ "config": { "field": { "type": "text", - "name": "nic", + "name": "http://wso2.org/claims/im", "hint": "", "label": "NIC", "required": true, @@ -590,7 +687,7 @@ "config": { "field": { "type": "submit", - "label": "Done" + "text": "Done" } } } diff --git a/features/admin.registration-flow-builder.v1/models/endpoints.ts b/features/admin.registration-flow-builder.v1/models/endpoints.ts new file mode 100644 index 00000000000..9cfd2fc66cf --- /dev/null +++ b/features/admin.registration-flow-builder.v1/models/endpoints.ts @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * Interface for the Registration Flow Builder feature resource endpoints. + */ +export interface RegistrationFlowBuilderResourceEndpointsInterface { + /** + * API to configure the registration flow. + * @example `https://{serverUrl}/t/{tenantDomain}/api/server/v1/reg-orchestration/config` + */ + configure: string; +} From 15498baa90092cf1ea5e05b230b9860eaa0330ef Mon Sep 17 00:00:00 2001 From: Brion Date: Wed, 18 Dec 2024 15:11:05 +0530 Subject: [PATCH 26/35] Update flow based on actions --- .../utils/transform-flow.ts | 107 ++++++++++++++---- 1 file changed, 84 insertions(+), 23 deletions(-) diff --git a/features/admin.flow-builder-core.v1/utils/transform-flow.ts b/features/admin.flow-builder-core.v1/utils/transform-flow.ts index 1f3b60dd38f..6bd72800072 100644 --- a/features/admin.flow-builder-core.v1/utils/transform-flow.ts +++ b/features/admin.flow-builder-core.v1/utils/transform-flow.ts @@ -25,10 +25,11 @@ import { Element as PayloadElement, Node as PayloadNode } from "../models/api"; +import { InputVariants } from "../models/component"; import { Element } from "../models/elements"; import { NodeData } from "../models/node"; -const DISPLAY_ONLY_ELEMENT_PROPERTIES: string[] = [ "display", "version", "variants" ]; +const DISPLAY_ONLY_ELEMENT_PROPERTIES: string[] = [ "display", "version", "variants", "deprecated", "meta" ]; const groupNodesIntoPages = (nodes: any[], edges: any[]): any[] => { const nodePages: Record = {}; @@ -60,7 +61,7 @@ const groupNodesIntoPages = (nodes: any[], edges: any[]): any[] => { } }); - return Object.keys(nodePages).map((pageId) => ({ + return Object.keys(nodePages).map(pageId => ({ id: pageId, nodes: nodePages[pageId] })); @@ -89,36 +90,94 @@ const transformFlow = (flowState: any): Payload => { }); flowNodes.forEach((node: XYFlowNode, index: number) => { - const nodeActions: PayloadAction[] = node.data?.components?.filter((component: Element) => component.category === "ACTION").map((action: Element) => { - let _action: any = { - id: action.id, - action: action.meta, - next: nodeIdToNextNodes[node.id] || [] - }; - - if (!action.meta && action?.config?.field?.type === "submit") { - _action = { - ..._action, - action: { - ..._action.action, - meta: { - actionType: "ATTRIBUTE_COLLECTION" + const nodeActions: PayloadAction[] = node.data?.components + ?.filter((component: Element) => component.category === "ACTION") + .map((action: Element) => { + let _action: any = { + id: action.id, + action: action.meta, + next: nodeIdToNextNodes[node.id] || [] + }; + + if (action?.config?.field?.type === "submit") { + // If there are password fields in the form, add a `CREDENTIAL_ONBOARDING` action type to all the submit actions + // TODO: Improve. + if ( + node.data?.components?.some( + (component: Element) => component?.variant === InputVariants.Password + ) + ) { + if (_action?.action?.executors) { + _action = { + ..._action, + action: { + ..._action.action, + executors: _action.action?.executors?.map((executor: any) => { + return { + ...executor, + meta: { + ...executor.meta, + actionType: "CREDENTIAL_ONBOARDING" + } + }; + }) + } + }; + } else { + _action = { + ..._action, + action: { + ..._action.action, + meta: { + actionType: "CREDENTIAL_ONBOARDING" + } + } + }; + } + } else { + if (_action?.action?.executors) { + _action = { + ..._action, + action: { + ..._action.action, + executors: _action.action?.executors?.map((executor: any) => { + return { + ...executor, + meta: { + ...executor.meta, + actionType: "ATTRIBUTE_COLLECTION" + } + }; + }) + } + }; + } else { + _action = { + ..._action, + action: { + ..._action.action, + meta: { + actionType: "ATTRIBUTE_COLLECTION" + } + } + }; } } - }; - } + } - return _action; - }); + return _action; + }); let currentBlock: PayloadBlock | null = null; const nodeElements: string[] = []; const nonBlockElements: string[] = []; // Identify the last ACTION with type "submit" - const lastSubmitActionIndex = node.data?.components?.map((component: Element, index: number) => ({ component, index })) + const lastSubmitActionIndex = node.data?.components + ?.map((component: Element, index: number) => ({ component, index })) .reverse() - .find(({ component }) => component.category === "ACTION" && component?.config?.field?.type === "submit")?.index; + .find(({ component }) => component.category === "ACTION" && component?.config?.field?.type === "submit") + ?.index; node.data?.components?.forEach((component: Element, index: number) => { if (currentBlock) { @@ -149,12 +208,14 @@ const transformFlow = (flowState: any): Payload => { payload.elements.push( ...(node.data?.components?.map((component: Element) => omit(component, DISPLAY_ONLY_ELEMENT_PROPERTIES) - )) as PayloadElement[] + ) as PayloadElement[]) ); }); // Add `next: ["COMPLETE"] to the last nodes' actions + // TODO: Improve. const lastNode = payload.nodes[payload.nodes.length - 1]; + lastNode.actions.forEach((action: PayloadAction) => { action.next = [ "COMPLETE" ]; }); From 5de72f37c54a783b1c0a541dbe1834b0618071ac Mon Sep 17 00:00:00 2001 From: Brion Date: Wed, 18 Dec 2024 15:16:30 +0530 Subject: [PATCH 27/35] Handle possible spreading issues --- .../admin.flow-builder-core.v1/utils/transform-flow.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/features/admin.flow-builder-core.v1/utils/transform-flow.ts b/features/admin.flow-builder-core.v1/utils/transform-flow.ts index 6bd72800072..0abc889fbff 100644 --- a/features/admin.flow-builder-core.v1/utils/transform-flow.ts +++ b/features/admin.flow-builder-core.v1/utils/transform-flow.ts @@ -116,7 +116,7 @@ const transformFlow = (flowState: any): Payload => { return { ...executor, meta: { - ...executor.meta, + ...(executor?.meta || {}), actionType: "CREDENTIAL_ONBOARDING" } }; @@ -127,8 +127,9 @@ const transformFlow = (flowState: any): Payload => { _action = { ..._action, action: { - ..._action.action, + ...(_action.action || {}), meta: { + ...(_action?.action?.meta || {}), actionType: "CREDENTIAL_ONBOARDING" } } @@ -144,7 +145,7 @@ const transformFlow = (flowState: any): Payload => { return { ...executor, meta: { - ...executor.meta, + ...(executor?.meta || {}), actionType: "ATTRIBUTE_COLLECTION" } }; @@ -155,8 +156,9 @@ const transformFlow = (flowState: any): Payload => { _action = { ..._action, action: { - ..._action.action, + ...(_action?.action || {}), meta: { + ...(_action?.action?.meta || {}), actionType: "ATTRIBUTE_COLLECTION" } } From ff298921b5e932a2ffdb637b2c85f61e1461c6c3 Mon Sep 17 00:00:00 2001 From: Brion Date: Wed, 18 Dec 2024 17:49:13 +0530 Subject: [PATCH 28/35] Update transformer --- .../utils/transform-flow.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/features/admin.flow-builder-core.v1/utils/transform-flow.ts b/features/admin.flow-builder-core.v1/utils/transform-flow.ts index 0abc889fbff..13a4a0b0ba8 100644 --- a/features/admin.flow-builder-core.v1/utils/transform-flow.ts +++ b/features/admin.flow-builder-core.v1/utils/transform-flow.ts @@ -79,14 +79,10 @@ const transformFlow = (flowState: any): Payload => { elements: [] }; - const nodeIdToNextNodes: Record = {}; + const nextNodeMap: Record = {}; - // Build a map of node IDs to their next connected nodes flowEdges.forEach((edge: any) => { - if (!nodeIdToNextNodes[edge.source]) { - nodeIdToNextNodes[edge.source] = []; - } - nodeIdToNextNodes[edge.source].push(edge.target); + nextNodeMap[edge.sourceHandle.replace("-NEXT", "").replace("-PREVIOUS", "")] = [ edge.target ]; }); flowNodes.forEach((node: XYFlowNode, index: number) => { @@ -96,7 +92,13 @@ const transformFlow = (flowState: any): Payload => { let _action: any = { id: action.id, action: action.meta, - next: nodeIdToNextNodes[node.id] || [] + next: node.data.components.map((component: Element) => { + if (component.id === action.id) { + if (nextNodeMap[component.id]) { + return nextNodeMap[component.id]; + } + } + }).flat().filter(Boolean) }; if (action?.config?.field?.type === "submit") { From 3f607eb5dc6e852ca4aefde4fb72f05df1a9b2a3 Mon Sep 17 00:00:00 2001 From: Brion Date: Thu, 19 Dec 2024 01:17:26 +0530 Subject: [PATCH 29/35] Fix button type selected state --- .../button-extended-properties.tsx | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/button-extended-properties.tsx b/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/button-extended-properties.tsx index 932e405b0b3..54214425290 100644 --- a/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/button-extended-properties.tsx +++ b/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/button-extended-properties.tsx @@ -57,8 +57,6 @@ const ButtonExtendedProperties: FunctionComponent(actions[0]?.types[0]); - return (
@@ -78,12 +76,6 @@ const ButtonExtendedProperties: FunctionComponent From f1d06c29ab2c801ffbc9174e5530f06e26f3351e Mon Sep 17 00:00:00 2001 From: Brion Date: Thu, 19 Dec 2024 01:50:34 +0530 Subject: [PATCH 30/35] Minor updates --- .../common-component-property-factory.tsx | 7 ++-- .../common-widget-property-factory.tsx | 3 +- .../element-properties.tsx | 5 +-- .../components/elements/nodes/step/step.tsx | 2 + .../data/actions.json | 33 +++++++++-------- .../data/components.json | 1 + .../utils/transform-flow.ts | 37 +++++++++++++------ .../api/configure-registration-flow.ts | 2 +- .../element-properties.tsx | 5 +++ .../element-property-factory.tsx | 3 +- .../field-extended-properties.tsx | 2 +- 11 files changed, 60 insertions(+), 40 deletions(-) diff --git a/features/admin.flow-builder-core.v1/components/element-property-panel/common-component-property-factory.tsx b/features/admin.flow-builder-core.v1/components/element-property-panel/common-component-property-factory.tsx index bdca3efd081..f8d42302c2d 100644 --- a/features/admin.flow-builder-core.v1/components/element-property-panel/common-component-property-factory.tsx +++ b/features/admin.flow-builder-core.v1/components/element-property-panel/common-component-property-factory.tsx @@ -45,11 +45,10 @@ export interface CommonComponentPropertyFactoryPropsInterface extends Identifiab /** * The event handler for the property change. * @param propertyKey - The key of the property. - * @param previousValue - The previous value of the property. * @param newValue - The new value of the property. * @param element - The element associated with the property. */ - onChange: (propertyKey: string, previousValue: any, newValue: any, element: Element) => void; + onChange: (propertyKey: string, newValue: any, element: Element) => void; } /** @@ -77,7 +76,7 @@ const CommonComponentPropertyFactory: FunctionComponent } label={ startCase(propertyKey) } onChange={ (e: ChangeEvent) => - onChange(`config.field.${propertyKey}`, propertyValue, e.target.checked, element) + onChange(`config.field.${propertyKey}`, e.target.checked, element) } data-componentid={ `${componentId}-${propertyKey}` } /> @@ -91,7 +90,7 @@ const CommonComponentPropertyFactory: FunctionComponent) => - onChange(`config.field.${propertyKey}`, propertyValue, e.target.value, element) + onChange(`config.field.${propertyKey}`, e.target.value, element) } data-componentid={ `${componentId}-${propertyKey}` } /> diff --git a/features/admin.flow-builder-core.v1/components/element-property-panel/common-widget-property-factory.tsx b/features/admin.flow-builder-core.v1/components/element-property-panel/common-widget-property-factory.tsx index 12d575d3615..9b02af2b84d 100644 --- a/features/admin.flow-builder-core.v1/components/element-property-panel/common-widget-property-factory.tsx +++ b/features/admin.flow-builder-core.v1/components/element-property-panel/common-widget-property-factory.tsx @@ -39,11 +39,10 @@ export interface CommonWidgetPropertyFactoryPropsInterface extends IdentifiableC /** * The event handler for the property change. * @param propertyKey - The key of the property. - * @param previousValue - The previous value of the property. * @param newValue - The new value of the property. * @param element - The element associated with the property. */ - onChange: (propertyKey: string, previousValue: any, newValue: any, element: Element) => void; + onChange: (propertyKey: string, newValue: any, element: Element) => void; } /** diff --git a/features/admin.flow-builder-core.v1/components/element-property-panel/element-properties.tsx b/features/admin.flow-builder-core.v1/components/element-property-panel/element-properties.tsx index 21a6783d19a..d1e4e6ecc25 100644 --- a/features/admin.flow-builder-core.v1/components/element-property-panel/element-properties.tsx +++ b/features/admin.flow-builder-core.v1/components/element-property-panel/element-properties.tsx @@ -40,11 +40,10 @@ export type CommonElementPropertiesPropsInterface = IdentifiableComponentInterfa /** * The event handler for the property change. * @param propertyKey - The key of the property. - * @param previousValue - The previous value of the property. * @param newValue - The new value of the property. * @param element - The element associated with the property. */ - onChange: (propertyKey: string, previousValue: any, newValue: any, element: Element) => void; + onChange: (propertyKey: string, newValue: any, element: Element) => void; /** * The event handler for the variant change. * @param variant - The variant of the element. @@ -92,7 +91,7 @@ const ElementProperties: FunctionComponent { + const handlePropertyChange = (propertyKey: string, newValue: any, element: Element) => { updateNodeData(lastInteractedNodeId, (node: any) => { const components: Component = node?.data?.components?.map((component: any) => { if (component.id === element.id) { diff --git a/features/admin.flow-builder-core.v1/components/elements/nodes/step/step.tsx b/features/admin.flow-builder-core.v1/components/elements/nodes/step/step.tsx index dfe913bb25c..83b413820bf 100644 --- a/features/admin.flow-builder-core.v1/components/elements/nodes/step/step.tsx +++ b/features/admin.flow-builder-core.v1/components/elements/nodes/step/step.tsx @@ -193,6 +193,8 @@ export const Step: FunctionComponent = ({ ) } onClick={ () => setLastInteractedElement(component) } { ...otherDragItemProps } + // TODO: Fix this. Temporary fix to prevent dragging issues. + draggable={ false } >
diff --git a/features/admin.flow-builder-core.v1/data/actions.json b/features/admin.flow-builder-core.v1/data/actions.json index 4db4702e29c..125c883e88e 100644 --- a/features/admin.flow-builder-core.v1/data/actions.json +++ b/features/admin.flow-builder-core.v1/data/actions.json @@ -37,34 +37,22 @@ "actionType": "VERIFICATION" }, "display": { - "label": "Verify email address", - "image": "https://www.svgrepo.com/show/408695/message-letter-mail-tick-check-e-mail.svg", + "label": "Verify OTP", + "image": "https://www.svgrepo.com/show/497630/verify.svg", "defaultVariant": "PRIMARY" } - }, - { - "type": "NEXT", - "meta": { - "actionType": "VERIFICATION" - }, - "display": { - "label": "Verify mobile number", - "image": "https://www.svgrepo.com/show/533121/mobile-check.svg", - "defaultVariant": "SECONDARY" - } } ] }, { - "category": "CREDENTIAL_ONBOARDING", + "category": "ONBOARDING", "display": { - "label": "Credential Onboarding", + "label": "Onboarding", "image": "" }, "types": [ { "type": "EXECUTOR", - "name": "PasswordOnboarder", "executors": [ { "name": "PasswordOnboarder" @@ -75,6 +63,19 @@ "image": "https://www.svgrepo.com/show/526627/password.svg", "defaultVariant": "PRIMARY" } + }, + { + "type": "EXECUTOR", + "executors": [ + { + "name": "EmailOTPVerifier" + } + ], + "display": { + "label": "Onboard Email", + "image": "https://www.svgrepo.com/show/479622/email-18.svg", + "defaultVariant": "PRIMARY" + } } ] }, diff --git a/features/admin.flow-builder-core.v1/data/components.json b/features/admin.flow-builder-core.v1/data/components.json index 2db17fa6104..d170410c384 100644 --- a/features/admin.flow-builder-core.v1/data/components.json +++ b/features/admin.flow-builder-core.v1/data/components.json @@ -31,6 +31,7 @@ }, "config": { "field": { + "name": "password", "type": "password", "hint": "", "label": "Password", diff --git a/features/admin.flow-builder-core.v1/utils/transform-flow.ts b/features/admin.flow-builder-core.v1/utils/transform-flow.ts index 13a4a0b0ba8..8e82220f2c3 100644 --- a/features/admin.flow-builder-core.v1/utils/transform-flow.ts +++ b/features/admin.flow-builder-core.v1/utils/transform-flow.ts @@ -28,6 +28,7 @@ import { import { InputVariants } from "../models/component"; import { Element } from "../models/elements"; import { NodeData } from "../models/node"; +import { ActionTypes } from "../models/actions"; const DISPLAY_ONLY_ELEMENT_PROPERTIES: string[] = [ "display", "version", "variants", "deprecated", "meta" ]; @@ -79,28 +80,35 @@ const transformFlow = (flowState: any): Payload => { elements: [] }; - const nextNodeMap: Record = {}; + const nodeNavigationMap: Record = {}; flowEdges.forEach((edge: any) => { - nextNodeMap[edge.sourceHandle.replace("-NEXT", "").replace("-PREVIOUS", "")] = [ edge.target ]; + nodeNavigationMap[edge.sourceHandle.replace("-NEXT", "").replace("-PREVIOUS", "")] = [ edge.target ]; }); flowNodes.forEach((node: XYFlowNode, index: number) => { const nodeActions: PayloadAction[] = node.data?.components ?.filter((component: Element) => component.category === "ACTION") .map((action: Element) => { + const navigation: string[] = node.data.components.map((component: Element) => { + if (component.id === action.id) { + if (nodeNavigationMap[component.id]) { + return nodeNavigationMap[component.id]; + } + } + }).flat().filter(Boolean); + let _action: any = { id: action.id, - action: action.meta, - next: node.data.components.map((component: Element) => { - if (component.id === action.id) { - if (nextNodeMap[component.id]) { - return nextNodeMap[component.id]; - } - } - }).flat().filter(Boolean) + action: action.meta }; + if (_action.action?.type === ActionTypes.Next || _action.action?.type === ActionTypes.Executor) { + _action.next = navigation; + } else if (_action.action?.type === ActionTypes.Previous) { + _action.previous = navigation; + } + if (action?.config?.field?.type === "submit") { // If there are password fields in the form, add a `CREDENTIAL_ONBOARDING` action type to all the submit actions // TODO: Improve. @@ -169,6 +177,11 @@ const transformFlow = (flowState: any): Payload => { } } + // TODO: Fix this. When the action type is not manually selected, `type` becomes `undefined`. + if (!_action.action?.type) { + _action.action.type = ActionTypes.Next; + } + return _action; }); @@ -221,7 +234,9 @@ const transformFlow = (flowState: any): Payload => { const lastNode = payload.nodes[payload.nodes.length - 1]; lastNode.actions.forEach((action: PayloadAction) => { - action.next = [ "COMPLETE" ]; + if (action.action?.type === ActionTypes.Next) { + action.next = [ "COMPLETE" ]; + } }); payload.flow.pages = groupNodesIntoPages(flowNodes, flowEdges); diff --git a/features/admin.registration-flow-builder.v1/api/configure-registration-flow.ts b/features/admin.registration-flow-builder.v1/api/configure-registration-flow.ts index 60161469829..8ad19dfaad6 100644 --- a/features/admin.registration-flow-builder.v1/api/configure-registration-flow.ts +++ b/features/admin.registration-flow-builder.v1/api/configure-registration-flow.ts @@ -47,7 +47,7 @@ const configureRegistrationFlow = (payload: Payload): Promise => }; if (payload.nodes?.length === 5) { - requestConfig.data = samplePayload; + // requestConfig.data = samplePayload; } return httpClient(requestConfig) diff --git a/features/admin.registration-flow-builder.v1/components/element-property-panel/element-properties.tsx b/features/admin.registration-flow-builder.v1/components/element-property-panel/element-properties.tsx index 2c21c5eae1c..5a3e6e70b98 100644 --- a/features/admin.registration-flow-builder.v1/components/element-property-panel/element-properties.tsx +++ b/features/admin.registration-flow-builder.v1/components/element-property-panel/element-properties.tsx @@ -27,6 +27,7 @@ import React, { ChangeEvent, FunctionComponent, ReactElement, useMemo } from "re import ElementPropertyFactory from "./element-property-factory"; import ButtonExtendedProperties from "./extended-properties/button-extended-properties"; import FieldExtendedProperties from "./extended-properties/field-extended-properties"; +import { InputVariants } from "@wso2is/admin.flow-builder-core.v1/models/component"; /** * Props interface of {@link ElementProperties} @@ -85,6 +86,10 @@ const ElementProperties: FunctionComponent = ({ switch (element.category) { case ElementCategories.Field: + if (element.variant === InputVariants.Password) { + return renderElementPropertyFactory(); + } + return ( <> void; + onChange: (propertyKey: string, newValue: any, element: Element) => void; } /** diff --git a/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/field-extended-properties.tsx b/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/field-extended-properties.tsx index 5556e5166bd..d6001a03914 100644 --- a/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/field-extended-properties.tsx +++ b/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/field-extended-properties.tsx @@ -61,7 +61,7 @@ const FieldExtendedProperties: FunctionComponent } value={ selectedValue } onChange={ (_: ChangeEvent, attribute: Attribute) => { - onChange("config.field.name", selectedAttribute?.claimURI, attribute?.claimURI, element); + onChange("config.field.name", attribute?.claimURI, element); setSelectedAttribute( attributes?.find((attribute: Attribute) => attribute?.claimURI === attribute?.claimURI) From 9607b198da485b16b27d4de8979956063f91bb78 Mon Sep 17 00:00:00 2001 From: Brion Date: Thu, 19 Dec 2024 02:53:26 +0530 Subject: [PATCH 31/35] Add support to add edges from outside --- .../components/decorated-visual-flow.tsx | 25 ++----- .../react-flow-overrides/base-edge.tsx | 73 +++++++++++++++++++ .../components/visual-flow.tsx | 42 ++++++++++- .../components/registration-flow-builder.tsx | 4 +- 4 files changed, 121 insertions(+), 23 deletions(-) create mode 100644 features/admin.flow-builder-core.v1/components/react-flow-overrides/base-edge.tsx diff --git a/features/admin.flow-builder-core.v1/components/decorated-visual-flow.tsx b/features/admin.flow-builder-core.v1/components/decorated-visual-flow.tsx index df0d34f4795..8f7c7fef689 100644 --- a/features/admin.flow-builder-core.v1/components/decorated-visual-flow.tsx +++ b/features/admin.flow-builder-core.v1/components/decorated-visual-flow.tsx @@ -20,30 +20,17 @@ import { IdentifiableComponentInterface } from "@wso2is/core/models"; import { DnDProvider } from "@wso2is/dnd"; import { ReactFlowProvider } from "@xyflow/react"; import classNames from "classnames"; -import React, { FunctionComponent, HTMLAttributes, ReactElement } from "react"; +import React, { FunctionComponent, ReactElement } from "react"; import ElementPanel from "./element-panel/element-panel"; import ElementPropertiesPanel from "./element-property-panel/element-property-panel"; -import VisualFlow from "./visual-flow"; +import VisualFlow, { VisualFlowPropsInterface } from "./visual-flow"; import useAuthenticationFlowBuilderCore from "../hooks/use-authentication-flow-builder-core-context"; -import { Elements } from "../models/elements"; -import { Payload } from "../models/api"; + /** * Props interface of {@link DecoratedVisualFlow} */ -export interface DecoratedVisualFlowPropsInterface - extends IdentifiableComponentInterface, - HTMLAttributes { - /** - * Flow elements. - */ - elements: Elements; - /** - * Callback to be fired when the flow is submitted. - * @param payload - Payload of the flow. - */ - onFlowSubmit: (payload: Payload) => void; -} +export type DecoratedVisualFlowPropsInterface = VisualFlowPropsInterface & IdentifiableComponentInterface; /** * Component to decorate the visual flow editor with the necessary providers. @@ -54,7 +41,6 @@ export interface DecoratedVisualFlowPropsInterface const DecoratedVisualFlow: FunctionComponent = ({ "data-componentid": componentId = "authentication-flow-visual-editor", elements, - onFlowSubmit, ...rest }: DecoratedVisualFlowPropsInterface): ReactElement => { const { isElementPanelOpen, isElementPropertiesPanelOpen } = useAuthenticationFlowBuilderCore(); @@ -63,13 +49,12 @@ const DecoratedVisualFlow: FunctionComponent
- + diff --git a/features/admin.flow-builder-core.v1/components/react-flow-overrides/base-edge.tsx b/features/admin.flow-builder-core.v1/components/react-flow-overrides/base-edge.tsx new file mode 100644 index 00000000000..cf3b0fe79a1 --- /dev/null +++ b/features/admin.flow-builder-core.v1/components/react-flow-overrides/base-edge.tsx @@ -0,0 +1,73 @@ +/** + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { IdentifiableComponentInterface } from "@wso2is/core/models"; +import { EdgeLabelRenderer, EdgeProps, BaseEdge as XYFlowBaseEdge, getBezierPath } from "@xyflow/react"; +import React, { FunctionComponent, ReactElement } from "react"; + +/** + * Props interface of {@link VisualFlow} + */ +export interface BaseEdgePropsInterface extends EdgeProps, IdentifiableComponentInterface {} + +/** + * A customized version of the BaseEdge component. + * + * @param props - Props injected to the component. + * @returns BaseEdge component. + */ +const BaseEdge: FunctionComponent = ({ + id, + sourceX, + sourceY, + targetX, + targetY, + sourcePosition, + targetPosition, + label, + ...rest +}: BaseEdgePropsInterface): ReactElement => { + const [ edgePath, labelX, labelY ] = getBezierPath({ + sourcePosition, + sourceX, + sourceY, + targetPosition, + targetX, + targetY + }); + + return ( + <> + + +
+ { label } +
+
+ + ); +}; + +export default BaseEdge; diff --git a/features/admin.flow-builder-core.v1/components/visual-flow.tsx b/features/admin.flow-builder-core.v1/components/visual-flow.tsx index 73fcffb8e63..fbef6abcd44 100644 --- a/features/admin.flow-builder-core.v1/components/visual-flow.tsx +++ b/features/admin.flow-builder-core.v1/components/visual-flow.tsx @@ -25,6 +25,7 @@ import { BackgroundVariant, Controls, Edge, + MarkerType, OnConnect, OnNodesDelete, ReactFlow, @@ -41,6 +42,7 @@ import { } from "@xyflow/react"; import React, { DragEvent, FC, FunctionComponent, ReactElement, useCallback, useMemo } from "react"; import NodeFactory from "./elements/nodes/node-factory"; +import BaseEdge from "./react-flow-overrides/base-edge"; import useAuthenticationFlowBuilderCore from "../hooks/use-authentication-flow-builder-core-context"; import { Payload } from "../models/api"; import { ElementCategories, Elements } from "../models/elements"; @@ -62,6 +64,18 @@ export interface VisualFlowPropsInterface extends IdentifiableComponentInterface * @param payload - Payload of the flow. */ onFlowSubmit: (payload: Payload) => void; + /** + * Custom edges to be rendered. + */ + customEdgeTypes?: { + [key: string]: Edge; + }; + /** + * Callback to be fired when an edge is resolved. + * @param connection - Connection object. + * @returns Edge object. + */ + onEdgeResolve?: (connection: any) => Edge; } /** @@ -73,7 +87,9 @@ export interface VisualFlowPropsInterface extends IdentifiableComponentInterface const VisualFlow: FunctionComponent = ({ "data-componentid": componentId = "authentication-flow-visual-flow", elements, + customEdgeTypes, onFlowSubmit, + onEdgeResolve, ...rest }: VisualFlowPropsInterface): ReactElement => { const [ nodes, setNodes, onNodesChange ] = useNodesState([]); @@ -121,7 +137,24 @@ const VisualFlow: FunctionComponent = ({ [ screenToFlowPosition, node?.type ] ); - const onConnect: OnConnect = useCallback((params: any) => setEdges((edges: Edge[]) => addEdge(params, edges)), []); + const onConnect: OnConnect = useCallback( + (connection: any) => { + let edge: Edge = onEdgeResolve && onEdgeResolve(connection); + + if (!edge) { + edge = { + ...connection, + markerEnd: { + type: MarkerType.Arrow + }, + type: "base-edge" + }; + } + + setEdges((edges: Edge[]) => addEdge(edge, edges)); + }, + [ setEdges ] + ); const onNodesDelete: OnNodesDelete = useCallback( (deleted: XYFlowNode[]) => { @@ -171,6 +204,12 @@ const VisualFlow: FunctionComponent = ({ }; const nodeTypes: { [key: string]: FC } = useMemo(() => generateNodeTypes(), []); + const edgeTypes: { [key: string]: FC } = useMemo(() => { + return { + "base-edge": BaseEdge, + ...customEdgeTypes + }; + }, []); return ( <> @@ -190,6 +229,7 @@ const VisualFlow: FunctionComponent = ({ nodes={ nodes } edges={ edges } nodeTypes={ nodeTypes as any } + edgeTypes={ edgeTypes as any } onNodesChange={ onNodesChange } onEdgesChange={ onEdgesChange } onConnect={ onConnect } diff --git a/features/admin.registration-flow-builder.v1/components/registration-flow-builder.tsx b/features/admin.registration-flow-builder.v1/components/registration-flow-builder.tsx index c417382ffb5..a4f25cd7ac5 100644 --- a/features/admin.registration-flow-builder.v1/components/registration-flow-builder.tsx +++ b/features/admin.registration-flow-builder.v1/components/registration-flow-builder.tsx @@ -20,7 +20,7 @@ import DecoratedVisualFlow from "@wso2is/admin.flow-builder-core.v1/components/d import { Payload } from "@wso2is/admin.flow-builder-core.v1/models/api"; import AuthenticationFlowBuilderCoreProvider from "@wso2is/admin.flow-builder-core.v1/providers/authentication-flow-builder-core-provider"; import { IdentifiableComponentInterface } from "@wso2is/core/models"; -import React, { FunctionComponent, HTMLAttributes, ReactElement } from "react"; +import React, { FunctionComponent, ReactElement } from "react"; import ElementProperties from "./element-property-panel/element-properties"; import ComponentFactory from "./elements/components/component-factory"; import configureRegistrationFlow from "../api/configure-registration-flow"; @@ -30,7 +30,7 @@ import RegistrationFlowBuilderProvider from "../providers/registration-flow-buil /** * Props interface of {@link RegistrationFlowBuilder} */ -export type RegistrationFlowBuilderPropsInterface = IdentifiableComponentInterface & HTMLAttributes; +export type RegistrationFlowBuilderPropsInterface = IdentifiableComponentInterface; /** * Entry point for the registration flow builder. From 7b9937a7d0b2193bac1ee3e4e4d16d64883f543f Mon Sep 17 00:00:00 2001 From: Brion Date: Fri, 20 Dec 2024 13:23:27 +0530 Subject: [PATCH 32/35] Update adapter styles --- .../components/adapters/button-adapter.tsx | 13 ++- .../react-flow-overrides/base-edge.tsx | 2 +- .../social-connection-edge.scss | 25 +++++ .../social-connection-edge.tsx | 94 +++++++++++++++++++ .../components/visual-flow.tsx | 10 +- .../constants/button-adapter-constants.ts | 30 ++++++ .../data/components.json | 1 + .../models/actions.ts | 5 + .../utils/get-known-edge-types.ts | 38 ++++++++ .../utils/get-known-element-properties.ts | 6 ++ .../utils/resolve-known-edges.ts | 56 +++++++++++ .../utils/transform-flow.ts | 62 ++++++------ 12 files changed, 307 insertions(+), 35 deletions(-) create mode 100644 features/admin.flow-builder-core.v1/components/react-flow-overrides/social-connection-edge.scss create mode 100644 features/admin.flow-builder-core.v1/components/react-flow-overrides/social-connection-edge.tsx create mode 100644 features/admin.flow-builder-core.v1/constants/button-adapter-constants.ts create mode 100644 features/admin.flow-builder-core.v1/utils/get-known-edge-types.ts create mode 100644 features/admin.flow-builder-core.v1/utils/resolve-known-edges.ts diff --git a/features/admin.flow-builder-core.v1/components/elements/components/adapters/button-adapter.tsx b/features/admin.flow-builder-core.v1/components/elements/components/adapters/button-adapter.tsx index 6ca02c0bad4..bc6f94fee73 100644 --- a/features/admin.flow-builder-core.v1/components/elements/components/adapters/button-adapter.tsx +++ b/features/admin.flow-builder-core.v1/components/elements/components/adapters/button-adapter.tsx @@ -20,6 +20,7 @@ import Button, { ButtonProps } from "@oxygen-ui/react/Button"; import { IdentifiableComponentInterface } from "@wso2is/core/models"; import { Handle, Position } from "@xyflow/react"; import React, { FunctionComponent, ReactElement } from "react"; +import ButtonAdapterConstants from "../../../../constants/button-adapter-constants"; import { ButtonVariants, Component } from "../../../../models/component"; import "./button-adapter.scss"; @@ -79,11 +80,19 @@ export const ButtonAdapter: FunctionComponent = ({ return (
- + - +
); }; diff --git a/features/admin.flow-builder-core.v1/components/react-flow-overrides/base-edge.tsx b/features/admin.flow-builder-core.v1/components/react-flow-overrides/base-edge.tsx index cf3b0fe79a1..b57e2cd5b1c 100644 --- a/features/admin.flow-builder-core.v1/components/react-flow-overrides/base-edge.tsx +++ b/features/admin.flow-builder-core.v1/components/react-flow-overrides/base-edge.tsx @@ -61,7 +61,7 @@ const BaseEdge: FunctionComponent = ({ position: "absolute", transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)` } } - className="nodrag nopan" + className="edge-label-renderer__social-connection-edge nodrag nopan nodrag nopan" > { label }
diff --git a/features/admin.flow-builder-core.v1/components/react-flow-overrides/social-connection-edge.scss b/features/admin.flow-builder-core.v1/components/react-flow-overrides/social-connection-edge.scss new file mode 100644 index 00000000000..d5986c8fb4b --- /dev/null +++ b/features/admin.flow-builder-core.v1/components/react-flow-overrides/social-connection-edge.scss @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +.edge-label-renderer__social-connection-edge { + z-index: 2; + + .oxygen-card .oxygen-card-content { + padding: 0; + } +} diff --git a/features/admin.flow-builder-core.v1/components/react-flow-overrides/social-connection-edge.tsx b/features/admin.flow-builder-core.v1/components/react-flow-overrides/social-connection-edge.tsx new file mode 100644 index 00000000000..35fa0a98b37 --- /dev/null +++ b/features/admin.flow-builder-core.v1/components/react-flow-overrides/social-connection-edge.tsx @@ -0,0 +1,94 @@ +/** + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { useTheme } from "@mui/material/styles"; +import Box from "@oxygen-ui/react/Box"; +import Card from "@oxygen-ui/react/Card"; +import CardContent from "@oxygen-ui/react/CardContent"; +import Typography from "@oxygen-ui/react/Typography"; +import { IdentifiableComponentInterface } from "@wso2is/core/models"; +import { EdgeLabelRenderer, EdgeProps, BaseEdge as XYFlowBaseEdge, getBezierPath } from "@xyflow/react"; +import React, { FunctionComponent, ReactElement, ReactNode } from "react"; +import "./social-connection-edge.scss"; + +/** + * Props interface of {@link VisualFlow} + */ +export interface SocialConnectionEdgePropsInterface extends EdgeProps, IdentifiableComponentInterface {} + +export const SocialConnectionEdgeKey: string = "social-connection-edge"; + +/** + * A customized version of the SocialConnectionEdge component. + * + * @param props - Props injected to the component. + * @returns SocialConnectionEdge component. + */ +const SocialConnectionEdge: FunctionComponent = ({ + id, + sourceX, + sourceY, + targetX, + targetY, + sourcePosition, + targetPosition, + data, + ...rest +}: SocialConnectionEdgePropsInterface): ReactElement => { + const theme = useTheme(); + const [ edgePath, labelX, labelY ] = getBezierPath({ + sourcePosition, + sourceX, + sourceY, + targetPosition, + targetX, + targetY + }); + + return ( + <> + + +
+ + + + + { data.label as ReactNode } + + + +
+
+ + ); +}; + +export default SocialConnectionEdge; diff --git a/features/admin.flow-builder-core.v1/components/visual-flow.tsx b/features/admin.flow-builder-core.v1/components/visual-flow.tsx index fbef6abcd44..c383ca612c3 100644 --- a/features/admin.flow-builder-core.v1/components/visual-flow.tsx +++ b/features/admin.flow-builder-core.v1/components/visual-flow.tsx @@ -47,7 +47,10 @@ import useAuthenticationFlowBuilderCore from "../hooks/use-authentication-flow-b import { Payload } from "../models/api"; import { ElementCategories, Elements } from "../models/elements"; import { Node } from "../models/node"; +import getKnownEdgeTypes from "../utils/get-known-edge-types"; +import resolveKnownEdges from "../utils/resolve-known-edges"; import transformFlow from "../utils/transform-flow"; +// IMPORTANT: `@xyflow/react/dist/style.css` should be at the top of the stylesheet import list. import "@xyflow/react/dist/style.css"; import "./visual-flow.scss"; @@ -75,7 +78,7 @@ export interface VisualFlowPropsInterface extends IdentifiableComponentInterface * @param connection - Connection object. * @returns Edge object. */ - onEdgeResolve?: (connection: any) => Edge; + onEdgeResolve?: (connection: any, nodes: XYFlowNode[]) => Edge; } /** @@ -139,7 +142,7 @@ const VisualFlow: FunctionComponent = ({ const onConnect: OnConnect = useCallback( (connection: any) => { - let edge: Edge = onEdgeResolve && onEdgeResolve(connection); + let edge: Edge = onEdgeResolve ? onEdgeResolve(connection, nodes) : resolveKnownEdges(connection, nodes); if (!edge) { edge = { @@ -153,7 +156,7 @@ const VisualFlow: FunctionComponent = ({ setEdges((edges: Edge[]) => addEdge(edge, edges)); }, - [ setEdges ] + [ setEdges, nodes ] ); const onNodesDelete: OnNodesDelete = useCallback( @@ -207,6 +210,7 @@ const VisualFlow: FunctionComponent = ({ const edgeTypes: { [key: string]: FC } = useMemo(() => { return { "base-edge": BaseEdge, + ...getKnownEdgeTypes(), ...customEdgeTypes }; }, []); diff --git a/features/admin.flow-builder-core.v1/constants/button-adapter-constants.ts b/features/admin.flow-builder-core.v1/constants/button-adapter-constants.ts new file mode 100644 index 00000000000..8c51cd9c483 --- /dev/null +++ b/features/admin.flow-builder-core.v1/constants/button-adapter-constants.ts @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +class ButtonAdapterConstants { + /** + * Private constructor to avoid object instantiation from outside + * the class. + */ + private constructor() { } + + public static readonly NEXT_BUTTON_HANDLE_SUFFIX: string = "-NEXT"; + public static readonly PREVIOUS_BUTTON_HANDLE_SUFFIX: string = "-PREVIOUS"; +} + +export default ButtonAdapterConstants; diff --git a/features/admin.flow-builder-core.v1/data/components.json b/features/admin.flow-builder-core.v1/data/components.json index d170410c384..ae15ac3a1bd 100644 --- a/features/admin.flow-builder-core.v1/data/components.json +++ b/features/admin.flow-builder-core.v1/data/components.json @@ -134,6 +134,7 @@ "field": { "type": "text", "hint": "", + "name": "otp", "label": "OTP", "required": false, "placeholder": "" diff --git a/features/admin.flow-builder-core.v1/models/actions.ts b/features/admin.flow-builder-core.v1/models/actions.ts index a08e33db476..89c7ad13382 100644 --- a/features/admin.flow-builder-core.v1/models/actions.ts +++ b/features/admin.flow-builder-core.v1/models/actions.ts @@ -39,6 +39,7 @@ export interface ActionType { export type Actions = Action[]; +// TODO: Re-evaluate the following enums export enum ActionCategories { Navigation = "NAVIGATION", Verification = "VERIFICATION", @@ -51,3 +52,7 @@ export enum ActionTypes { Previous = "PREVIOUS", Executor = "EXECUTOR" } + +export enum ActionVariants { + SOCIAL = "SOCIAL" +} diff --git a/features/admin.flow-builder-core.v1/utils/get-known-edge-types.ts b/features/admin.flow-builder-core.v1/utils/get-known-edge-types.ts new file mode 100644 index 00000000000..b9dca62bde9 --- /dev/null +++ b/features/admin.flow-builder-core.v1/utils/get-known-edge-types.ts @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Edge } from "@xyflow/react"; +import { FC } from "react"; +import SocialConnectionEdge, { + SocialConnectionEdgeKey +} from "../components/react-flow-overrides/social-connection-edge"; + +/** + * Returns a mapping of known edge types. + * + * @returns An object with edge type identifiers. + */ +const getKnownEdgeTypes = (): { + [key: string]: FC; +} => { + return { + [SocialConnectionEdgeKey]: SocialConnectionEdge + }; +}; + +export default getKnownEdgeTypes; diff --git a/features/admin.flow-builder-core.v1/utils/get-known-element-properties.ts b/features/admin.flow-builder-core.v1/utils/get-known-element-properties.ts index 97b9367205a..2c5cfbcd657 100644 --- a/features/admin.flow-builder-core.v1/utils/get-known-element-properties.ts +++ b/features/admin.flow-builder-core.v1/utils/get-known-element-properties.ts @@ -19,6 +19,12 @@ import { ComponentTypes } from "../models/component"; import { Element } from "../models/elements"; +/** + * Returns a mapping of known properties for a given element. + * + * @param element - The element for which to get the known properties. + * @returns An object with known element properties. + */ const getKnownElementProperties = (element: Element): Record => { if (element.type === ComponentTypes.Button) { return { diff --git a/features/admin.flow-builder-core.v1/utils/resolve-known-edges.ts b/features/admin.flow-builder-core.v1/utils/resolve-known-edges.ts new file mode 100644 index 00000000000..721b0c0afd3 --- /dev/null +++ b/features/admin.flow-builder-core.v1/utils/resolve-known-edges.ts @@ -0,0 +1,56 @@ +/** + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Edge, Node } from "@xyflow/react"; +import { SocialConnectionEdgeKey } from "../components/react-flow-overrides/social-connection-edge"; +import ButtonAdapterConstants from "../constants/button-adapter-constants"; +import { ActionVariants } from "../models/actions"; +import { Element, ElementCategories } from "../models/elements"; + +/** + * Resolves known edges based on the connection and nodes provided. + * + * @param connection - The edge connection to resolve. + * @param nodes - The list of nodes to search for the source element. + * @returns The resolved edge with additional data if it matches known criteria, otherwise null. + */ +const resolveKnownEdges = (connection: Edge, nodes: Node[]): Edge => { + const sourceElementId: string = connection.sourceHandle + .replace(ButtonAdapterConstants.NEXT_BUTTON_HANDLE_SUFFIX, "") + .replace(ButtonAdapterConstants.PREVIOUS_BUTTON_HANDLE_SUFFIX, ""); + const sourceElement: Element | undefined = nodes + .flatMap((node: Node) => node?.data?.components as Element[]) + .find((component: Element) => component?.id === sourceElementId); + + if (sourceElement?.category === ElementCategories.Action) { + if (sourceElement.variant === ActionVariants.SOCIAL) { + return { + ...connection, + data: { + img: "https://www.svgrepo.com/show/475656/google-color.svg", + label: "Sign in with Google" + }, + type: SocialConnectionEdgeKey + }; + } + } + + return null; +}; + +export default resolveKnownEdges; diff --git a/features/admin.flow-builder-core.v1/utils/transform-flow.ts b/features/admin.flow-builder-core.v1/utils/transform-flow.ts index 8e82220f2c3..b44f6fc6f1a 100644 --- a/features/admin.flow-builder-core.v1/utils/transform-flow.ts +++ b/features/admin.flow-builder-core.v1/utils/transform-flow.ts @@ -29,6 +29,7 @@ import { InputVariants } from "../models/component"; import { Element } from "../models/elements"; import { NodeData } from "../models/node"; import { ActionTypes } from "../models/actions"; +import set from "lodash-es/set"; const DISPLAY_ONLY_ELEMENT_PROPERTIES: string[] = [ "display", "version", "variants", "deprecated", "meta" ]; @@ -113,6 +114,7 @@ const transformFlow = (flowState: any): Payload => { // If there are password fields in the form, add a `CREDENTIAL_ONBOARDING` action type to all the submit actions // TODO: Improve. if ( + _action.action?.type === ActionTypes.Next && node.data?.components?.some( (component: Element) => component?.variant === InputVariants.Password ) @@ -146,40 +148,42 @@ const transformFlow = (flowState: any): Payload => { }; } } else { - if (_action?.action?.executors) { - _action = { - ..._action, - action: { - ..._action.action, - executors: _action.action?.executors?.map((executor: any) => { - return { - ...executor, - meta: { - ...(executor?.meta || {}), - actionType: "ATTRIBUTE_COLLECTION" - } - }; - }) - } - }; - } else { - _action = { - ..._action, - action: { - ...(_action?.action || {}), - meta: { - ...(_action?.action?.meta || {}), - actionType: "ATTRIBUTE_COLLECTION" - } - } - }; - } + // const actionType: string = _action.action?.meta?.actionType || "ATTRIBUTE_COLLECTION"; + + // if (_action?.action?.executors) { + // _action = { + // ..._action, + // action: { + // ..._action.action, + // executors: _action.action?.executors?.map((executor: any) => { + // return { + // ...executor, + // meta: { + // ...(executor?.meta || {}), + // actionType: actionType + // } + // }; + // }) + // } + // }; + // } else { + // _action = { + // ..._action, + // action: { + // ...(_action?.action || {}), + // meta: { + // ...(_action?.action?.meta || {}), + // actionType: actionType + // } + // } + // }; + // } } } // TODO: Fix this. When the action type is not manually selected, `type` becomes `undefined`. if (!_action.action?.type) { - _action.action.type = ActionTypes.Next; + set(_action, "action.type", ActionTypes.Next); } return _action; From e1eb44ea9f6cb30335279c9de48aefd369afa795 Mon Sep 17 00:00:00 2001 From: Brion Date: Fri, 20 Dec 2024 14:10:47 +0530 Subject: [PATCH 33/35] Fix ESLint issues --- .../api/use-get-flow-builder-core-actions.ts | 2 +- .../social-connection-edge.tsx | 4 +- .../components/visual-flow.tsx | 3 - .../utils/transform-flow.ts | 67 ++++++++++--------- .../element-properties.tsx | 6 +- 5 files changed, 42 insertions(+), 40 deletions(-) diff --git a/features/admin.flow-builder-core.v1/api/use-get-flow-builder-core-actions.ts b/features/admin.flow-builder-core.v1/api/use-get-flow-builder-core-actions.ts index 67c533a7203..d903d527dbc 100644 --- a/features/admin.flow-builder-core.v1/api/use-get-flow-builder-core-actions.ts +++ b/features/admin.flow-builder-core.v1/api/use-get-flow-builder-core-actions.ts @@ -17,7 +17,7 @@ */ import { RequestErrorInterface, RequestResultInterface } from "@wso2is/admin.core.v1/hooks/use-request"; -import actions from "../data/actions.json";; +import actions from "../data/actions.json"; import { Actions } from "../models/actions"; /** diff --git a/features/admin.flow-builder-core.v1/components/react-flow-overrides/social-connection-edge.tsx b/features/admin.flow-builder-core.v1/components/react-flow-overrides/social-connection-edge.tsx index 35fa0a98b37..2ad6b238adb 100644 --- a/features/admin.flow-builder-core.v1/components/react-flow-overrides/social-connection-edge.tsx +++ b/features/admin.flow-builder-core.v1/components/react-flow-overrides/social-connection-edge.tsx @@ -16,7 +16,7 @@ * under the License. */ -import { useTheme } from "@mui/material/styles"; +import { Theme, useTheme } from "@mui/material/styles"; import Box from "@oxygen-ui/react/Box"; import Card from "@oxygen-ui/react/Card"; import CardContent from "@oxygen-ui/react/CardContent"; @@ -50,7 +50,7 @@ const SocialConnectionEdge: FunctionComponent { - const theme = useTheme(); + const theme: Theme = useTheme(); const [ edgePath, labelX, labelY ] = getBezierPath({ sourcePosition, sourceX, diff --git a/features/admin.flow-builder-core.v1/components/visual-flow.tsx b/features/admin.flow-builder-core.v1/components/visual-flow.tsx index c383ca612c3..abbe7be7b57 100644 --- a/features/admin.flow-builder-core.v1/components/visual-flow.tsx +++ b/features/admin.flow-builder-core.v1/components/visual-flow.tsx @@ -188,9 +188,6 @@ const VisualFlow: FunctionComponent = ({ const handlePublish = (): void => { const flow: any = toObject(); - console.log("Raw", JSON.stringify(flow, null, 2)); - console.log("Transformed", JSON.stringify(transformFlow(flow), null, 2)); - onFlowSubmit(transformFlow(flow)); }; diff --git a/features/admin.flow-builder-core.v1/utils/transform-flow.ts b/features/admin.flow-builder-core.v1/utils/transform-flow.ts index b44f6fc6f1a..a4a5e3ae33e 100644 --- a/features/admin.flow-builder-core.v1/utils/transform-flow.ts +++ b/features/admin.flow-builder-core.v1/utils/transform-flow.ts @@ -16,8 +16,10 @@ * under the License. */ -import { Node as XYFlowNode } from "@xyflow/react"; +import { Edge, Node as XYFlowNode } from "@xyflow/react"; import omit from "lodash-es/omit"; +import set from "lodash-es/set"; +import { ActionTypes } from "../models/actions"; import { Payload, Action as PayloadAction, @@ -28,14 +30,12 @@ import { import { InputVariants } from "../models/component"; import { Element } from "../models/elements"; import { NodeData } from "../models/node"; -import { ActionTypes } from "../models/actions"; -import set from "lodash-es/set"; const DISPLAY_ONLY_ELEMENT_PROPERTIES: string[] = [ "display", "version", "variants", "deprecated", "meta" ]; const groupNodesIntoPages = (nodes: any[], edges: any[]): any[] => { const nodePages: Record = {}; - const visitedNodes = new Set(); + const visitedNodes: Set = new Set(); const traverseNodes = (nodeId: string, pageId: string) => { if (visitedNodes.has(nodeId)) return; @@ -46,10 +46,10 @@ const groupNodesIntoPages = (nodes: any[], edges: any[]): any[] => { } nodePages[pageId].push(nodeId); - const connectedEdges = edges.filter((edge: any) => edge.source === nodeId); + const connectedEdges: Edge[] = edges.filter((edge: any) => edge.source === nodeId); connectedEdges.forEach((edge: any) => { - const nextNodeId = edge.target; + const nextNodeId: string = edge.target; traverseNodes(nextNodeId, pageId + 1); }); @@ -57,13 +57,13 @@ const groupNodesIntoPages = (nodes: any[], edges: any[]): any[] => { nodes.forEach((node: any) => { if (!visitedNodes.has(node.id)) { - const pageId = `flow-page-${Object.keys(nodePages).length + 1}`; + const pageId: string = `flow-page-${Object.keys(nodePages).length + 1}`; traverseNodes(node.id, pageId); } }); - return Object.keys(nodePages).map(pageId => ({ + return Object.keys(nodePages).map((pageId: string) => ({ id: pageId, nodes: nodePages[pageId] })); @@ -73,12 +73,12 @@ const transformFlow = (flowState: any): Payload => { const { nodes: flowNodes, edges: flowEdges } = flowState; const payload: Payload = { + blocks: [], + elements: [], flow: { pages: [] }, - nodes: [], - blocks: [], - elements: [] + nodes: [] }; const nodeNavigationMap: Record = {}; @@ -87,21 +87,24 @@ const transformFlow = (flowState: any): Payload => { nodeNavigationMap[edge.sourceHandle.replace("-NEXT", "").replace("-PREVIOUS", "")] = [ edge.target ]; }); - flowNodes.forEach((node: XYFlowNode, index: number) => { + flowNodes.forEach((node: XYFlowNode) => { const nodeActions: PayloadAction[] = node.data?.components ?.filter((component: Element) => component.category === "ACTION") .map((action: Element) => { - const navigation: string[] = node.data.components.map((component: Element) => { - if (component.id === action.id) { - if (nodeNavigationMap[component.id]) { - return nodeNavigationMap[component.id]; + const navigation: string[] = node.data.components + .map((component: Element) => { + if (component.id === action.id) { + if (nodeNavigationMap[component.id]) { + return nodeNavigationMap[component.id]; + } } - } - }).flat().filter(Boolean); + }) + .flat() + .filter(Boolean); let _action: any = { - id: action.id, - action: action.meta + action: action.meta, + id: action.id }; if (_action.action?.type === ActionTypes.Next || _action.action?.type === ActionTypes.Executor) { @@ -111,8 +114,8 @@ const transformFlow = (flowState: any): Payload => { } if (action?.config?.field?.type === "submit") { - // If there are password fields in the form, add a `CREDENTIAL_ONBOARDING` action type to all the submit actions - // TODO: Improve. + // TODO: Improve this. If there are password fields in the form, add a `CREDENTIAL_ONBOARDING` + // action type to all the submit actions. if ( _action.action?.type === ActionTypes.Next && node.data?.components?.some( @@ -149,7 +152,6 @@ const transformFlow = (flowState: any): Payload => { } } else { // const actionType: string = _action.action?.meta?.actionType || "ATTRIBUTE_COLLECTION"; - // if (_action?.action?.executors) { // _action = { // ..._action, @@ -191,14 +193,15 @@ const transformFlow = (flowState: any): Payload => { let currentBlock: PayloadBlock | null = null; const nodeElements: string[] = []; - const nonBlockElements: string[] = []; // Identify the last ACTION with type "submit" - const lastSubmitActionIndex = node.data?.components + const lastSubmitActionIndex: number = node.data?.components ?.map((component: Element, index: number) => ({ component, index })) .reverse() - .find(({ component }) => component.category === "ACTION" && component?.config?.field?.type === "submit") - ?.index; + .find( + ({ component }: { component: Element }) => + component.category === "ACTION" && component?.config?.field?.type === "submit" + )?.index; node.data?.components?.forEach((component: Element, index: number) => { if (currentBlock) { @@ -209,8 +212,8 @@ const transformFlow = (flowState: any): Payload => { } else { if (component.category === "FIELD") { currentBlock = { - id: `flow-block-${payload.blocks.length + 1}`, - elements: [ component.id ] + elements: [ component.id ], + id: `flow-block-${payload.blocks.length + 1}` }; payload.blocks.push(currentBlock); nodeElements.push(currentBlock.id); @@ -221,9 +224,9 @@ const transformFlow = (flowState: any): Payload => { }); payload.nodes.push({ - id: node.id, + actions: nodeActions, elements: nodeElements, - actions: nodeActions + id: node.id } as PayloadNode); payload.elements.push( @@ -235,7 +238,7 @@ const transformFlow = (flowState: any): Payload => { // Add `next: ["COMPLETE"] to the last nodes' actions // TODO: Improve. - const lastNode = payload.nodes[payload.nodes.length - 1]; + const lastNode: PayloadNode = payload.nodes[payload.nodes.length - 1]; lastNode.actions.forEach((action: PayloadAction) => { if (action.action?.type === ActionTypes.Next) { diff --git a/features/admin.registration-flow-builder.v1/components/element-property-panel/element-properties.tsx b/features/admin.registration-flow-builder.v1/components/element-property-panel/element-properties.tsx index 5a3e6e70b98..92e50151d38 100644 --- a/features/admin.registration-flow-builder.v1/components/element-property-panel/element-properties.tsx +++ b/features/admin.registration-flow-builder.v1/components/element-property-panel/element-properties.tsx @@ -18,8 +18,11 @@ import Autocomplete, { AutocompleteRenderInputParams } from "@oxygen-ui/react/Autocomplete"; import TextField from "@oxygen-ui/react/TextField"; -import { CommonElementPropertiesPropsInterface } from "@wso2is/admin.flow-builder-core.v1/components/element-property-panel/element-properties"; +import { + CommonElementPropertiesPropsInterface +} from "@wso2is/admin.flow-builder-core.v1/components/element-property-panel/element-properties"; import { FieldKey, FieldValue } from "@wso2is/admin.flow-builder-core.v1/models/base"; +import { InputVariants } from "@wso2is/admin.flow-builder-core.v1/models/component"; import { Element, ElementCategories } from "@wso2is/admin.flow-builder-core.v1/models/elements"; import { IdentifiableComponentInterface } from "@wso2is/core/models"; import isEmpty from "lodash-es/isEmpty"; @@ -27,7 +30,6 @@ import React, { ChangeEvent, FunctionComponent, ReactElement, useMemo } from "re import ElementPropertyFactory from "./element-property-factory"; import ButtonExtendedProperties from "./extended-properties/button-extended-properties"; import FieldExtendedProperties from "./extended-properties/field-extended-properties"; -import { InputVariants } from "@wso2is/admin.flow-builder-core.v1/models/component"; /** * Props interface of {@link ElementProperties} From 8679089b018df8a23967c4b2af397fe463beb30a Mon Sep 17 00:00:00 2001 From: Brion Date: Fri, 20 Dec 2024 14:26:14 +0530 Subject: [PATCH 34/35] Fix ESLint issues --- .../element-property-panel/element-properties.tsx | 3 +-- .../api/configure-registration-flow.ts | 4 +--- .../extended-properties/button-extended-properties.tsx | 2 +- .../extended-properties/field-extended-properties.tsx | 6 ++++-- .../components/registration-flow-builder.tsx | 3 ++- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/features/admin.flow-builder-core.v1/components/element-property-panel/element-properties.tsx b/features/admin.flow-builder-core.v1/components/element-property-panel/element-properties.tsx index d1e4e6ecc25..96b12c31aeb 100644 --- a/features/admin.flow-builder-core.v1/components/element-property-panel/element-properties.tsx +++ b/features/admin.flow-builder-core.v1/components/element-property-panel/element-properties.tsx @@ -58,8 +58,7 @@ export type CommonElementPropertiesPropsInterface = IdentifiableComponentInterfa * @returns The ElementProperties component. */ const ElementProperties: FunctionComponent> = ({ - "data-componentid": componentId = "element-properties", - ...rest + "data-componentid": componentId = "element-properties" }: Partial): ReactElement => { const { updateNodeData } = useReactFlow(); const { diff --git a/features/admin.registration-flow-builder.v1/api/configure-registration-flow.ts b/features/admin.registration-flow-builder.v1/api/configure-registration-flow.ts index 8ad19dfaad6..e2505202048 100644 --- a/features/admin.registration-flow-builder.v1/api/configure-registration-flow.ts +++ b/features/admin.registration-flow-builder.v1/api/configure-registration-flow.ts @@ -21,7 +21,6 @@ import { RequestConfigInterface } from "@wso2is/admin.core.v1/hooks/use-request" import { Payload } from "@wso2is/admin.flow-builder-core.v1/models/api"; import { HttpMethods } from "@wso2is/core/models"; import { AxiosError, AxiosResponse } from "axios"; -import samplePayload from "../data/payload.json"; const httpClient: HttpClientInstance = AsgardeoSPAClient.getInstance().httpRequest.bind( AsgardeoSPAClient.getInstance() @@ -65,8 +64,7 @@ const configureRegistrationFlow = (payload: Payload): Promise => return Promise.resolve(response.data); }) - .catch((error: AxiosError) => { - debugger; + .catch((_error: AxiosError) => { // throw new IdentityAppsApiException( // TenantConstants.TENANT_CREATION_ERROR, // error.stack, diff --git a/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/button-extended-properties.tsx b/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/button-extended-properties.tsx index 54214425290..42d245439f8 100644 --- a/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/button-extended-properties.tsx +++ b/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/button-extended-properties.tsx @@ -32,7 +32,7 @@ import { Action, ActionType } from "@wso2is/admin.flow-builder-core.v1/models/ac import { IdentifiableComponentInterface } from "@wso2is/core/models"; import classNames from "classnames"; import isEqual from "lodash-es/isEqual"; -import React, { FunctionComponent, ReactElement, useState } from "react"; +import React, { FunctionComponent, ReactElement } from "react"; import useGetRegistrationFlowCoreActions from "../../../api/use-get-registration-flow-builder-actions"; import "./button-extended-properties.scss"; diff --git a/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/field-extended-properties.tsx b/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/field-extended-properties.tsx index d6001a03914..3eb15f71673 100644 --- a/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/field-extended-properties.tsx +++ b/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/field-extended-properties.tsx @@ -19,7 +19,9 @@ import Autocomplete, { AutocompleteRenderInputParams } from "@oxygen-ui/react/Autocomplete"; import Stack from "@oxygen-ui/react/Stack"; import TextField from "@oxygen-ui/react/TextField"; -import { CommonElementPropertiesPropsInterface } from "@wso2is/admin.flow-builder-core.v1/components/element-property-panel/element-properties"; +import { + CommonElementPropertiesPropsInterface +} from "@wso2is/admin.flow-builder-core.v1/components/element-property-panel/element-properties"; import { IdentifiableComponentInterface } from "@wso2is/core/models"; import React, { ChangeEvent, FunctionComponent, ReactElement, useMemo, useState } from "react"; import useGetSupportedProfileAttributes from "../../../api/use-get-supported-profile-attributes"; @@ -43,7 +45,7 @@ const FieldExtendedProperties: FunctionComponent { const { data: attributes } = useGetSupportedProfileAttributes(); - const [ selectedAttribute, setSelectedAttribute ] = useState(null); + const [ setSelectedAttribute ] = useState(null); const selectedValue: Attribute = useMemo(() => { return attributes?.find( diff --git a/features/admin.registration-flow-builder.v1/components/registration-flow-builder.tsx b/features/admin.registration-flow-builder.v1/components/registration-flow-builder.tsx index a4f25cd7ac5..c4fb84a8b1d 100644 --- a/features/admin.registration-flow-builder.v1/components/registration-flow-builder.tsx +++ b/features/admin.registration-flow-builder.v1/components/registration-flow-builder.tsx @@ -18,7 +18,8 @@ import DecoratedVisualFlow from "@wso2is/admin.flow-builder-core.v1/components/decorated-visual-flow"; import { Payload } from "@wso2is/admin.flow-builder-core.v1/models/api"; -import AuthenticationFlowBuilderCoreProvider from "@wso2is/admin.flow-builder-core.v1/providers/authentication-flow-builder-core-provider"; +import AuthenticationFlowBuilderCoreProvider from + "@wso2is/admin.flow-builder-core.v1/providers/authentication-flow-builder-core-provider"; import { IdentifiableComponentInterface } from "@wso2is/core/models"; import React, { FunctionComponent, ReactElement } from "react"; import ElementProperties from "./element-property-panel/element-properties"; From d8b6445f43955f93947d16495f9fbc276ea13f52 Mon Sep 17 00:00:00 2001 From: Brion Date: Fri, 20 Dec 2024 15:06:57 +0530 Subject: [PATCH 35/35] Fix TS issue --- .../extended-properties/field-extended-properties.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/field-extended-properties.tsx b/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/field-extended-properties.tsx index 3eb15f71673..3a0e3ee1526 100644 --- a/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/field-extended-properties.tsx +++ b/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/field-extended-properties.tsx @@ -45,7 +45,7 @@ const FieldExtendedProperties: FunctionComponent { const { data: attributes } = useGetSupportedProfileAttributes(); - const [ setSelectedAttribute ] = useState(null); + const [ _, setSelectedAttribute ] = useState(null); const selectedValue: Attribute = useMemo(() => { return attributes?.find(