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..d903d527dbc --- /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/components/decorated-visual-flow.tsx b/features/admin.flow-builder-core.v1/components/decorated-visual-flow.tsx index e34cbdae502..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,24 +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"; + /** * Props interface of {@link DecoratedVisualFlow} */ -export interface DecoratedVisualFlowPropsInterface - extends IdentifiableComponentInterface, - HTMLAttributes { - /** - * Flow elements. - */ - elements: Elements; -} +export type DecoratedVisualFlowPropsInterface = VisualFlowPropsInterface & IdentifiableComponentInterface; /** * Component to decorate the visual flow editor with the necessary providers. @@ -56,13 +49,12 @@ const DecoratedVisualFlow: FunctionComponent
- + 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..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; } /** @@ -74,10 +73,10 @@ const CommonComponentPropertyFactory: FunctionComponent } + control={ } label={ startCase(propertyKey) } onChange={ (e: ChangeEvent) => - onChange(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(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 58cbda17d83..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 @@ -16,25 +16,40 @@ * 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 merge from "lodash-es/merge"; +import set from "lodash-es/set"; +import React, { FunctionComponent, ReactElement } from "react"; import useAuthenticationFlowBuilderCore from "../../hooks/use-authentication-flow-builder-core-context"; -import { Base } from "../../models/base"; +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 newValue - The new value of the property. + * @param element - The element associated with the property. + */ + onChange: (propertyKey: string, 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. @@ -42,10 +57,9 @@ export type ElementPropertiesPropsInterface = IdentifiableComponentInterface & H * @param props - Props injected to the component. * @returns The ElementProperties component. */ -const ElementProperties: FunctionComponent = ({ - "data-componentid": componentId = "element-properties", - ...rest -}: ElementPropertiesPropsInterface): ReactElement => { +const ElementProperties: FunctionComponent> = ({ + "data-componentid": componentId = "element-properties" +}: Partial): ReactElement => { const { updateNodeData } = useReactFlow(); const { lastInteractedElement, @@ -54,8 +68,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 @@ -64,19 +76,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 @@ -84,11 +90,11 @@ const ElementProperties: FunctionComponent = ({ }); }; - const handlePropertyChange = (propertyKey: string, previousValue: any, newValue: any, element: Element) => { + 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) { - component.config.field[propertyKey] = newValue; + set(component, propertyKey, newValue); } return component; @@ -101,33 +107,15 @@ const ElementProperties: FunctionComponent = ({ }; return ( -
+
{ 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..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 @@ -18,8 +18,11 @@ 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"; /** * Props interface of {@link ButtonAdapter} @@ -50,25 +53,47 @@ 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/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/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/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.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/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/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..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 } >
@@ -209,7 +211,6 @@ export const Step: 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..b57e2cd5b1c --- /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/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..2ad6b238adb --- /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 { 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"; +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: 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.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/features/admin.flow-builder-core.v1/components/visual-flow.tsx b/features/admin.flow-builder-core.v1/components/visual-flow.tsx index 65ea39fd6f7..abbe7be7b57 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,17 +42,44 @@ 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 BaseEdge from "./react-flow-overrides/base-edge"; import useAuthenticationFlowBuilderCore from "../hooks/use-authentication-flow-builder-core-context"; -import { ElementCategories } from "../models/elements"; +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"; /** * Props interface of {@link VisualFlow} */ -export type VisualFlowPropsInterface = IdentifiableComponentInterface & ReactFlowProps; +export interface VisualFlowPropsInterface extends IdentifiableComponentInterface, ReactFlowProps { + /** + * Flow elements. + */ + elements: Elements; + /** + * Callback to be fired when the flow is submitted. + * @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, nodes: XYFlowNode[]) => Edge; +} /** * Wrapper component for React Flow used in the Visual Editor. @@ -61,6 +89,10 @@ export type VisualFlowPropsInterface = IdentifiableComponentInterface & ReactFlo */ const VisualFlow: FunctionComponent = ({ "data-componentid": componentId = "authentication-flow-visual-flow", + elements, + customEdgeTypes, + onFlowSubmit, + onEdgeResolve, ...rest }: VisualFlowPropsInterface): ReactElement => { const [ nodes, setNodes, onNodesChange ] = useNodesState([]); @@ -68,7 +100,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(); @@ -109,7 +140,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, nodes) : resolveKnownEdges(connection, nodes); + + if (!edge) { + edge = { + ...connection, + markerEnd: { + type: MarkerType.Arrow + }, + type: "base-edge" + }; + } + + setEdges((edges: Edge[]) => addEdge(edge, edges)); + }, + [ setEdges, nodes ] + ); const onNodesDelete: OnNodesDelete = useCallback( (deleted: XYFlowNode[]) => { @@ -138,15 +186,17 @@ const VisualFlow: FunctionComponent = ({ // TODO: Handle the submit const handlePublish = (): void => { - const _flow: any = toObject(); + const flow: any = toObject(); + + onFlowSubmit(transformFlow(flow)); }; 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; @@ -154,6 +204,13 @@ const VisualFlow: FunctionComponent = ({ }; const nodeTypes: { [key: string]: FC } = useMemo(() => generateNodeTypes(), []); + const edgeTypes: { [key: string]: FC } = useMemo(() => { + return { + "base-edge": BaseEdge, + ...getKnownEdgeTypes(), + ...customEdgeTypes + }; + }, []); return ( <> @@ -173,6 +230,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.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/actions.json b/features/admin.flow-builder-core.v1/data/actions.json new file mode 100644 index 00000000000..125c883e88e --- /dev/null +++ b/features/admin.flow-builder-core.v1/data/actions.json @@ -0,0 +1,107 @@ +[ + { + "category": "NAVIGATION", + "display": { + "label": "Navigation", + "image": "" + }, + "types": [ + { + "type": "NEXT", + "display": { + "label": "Next", + "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", + "defaultVariant": "SECONDARY" + } + } + ] + }, + { + "category": "VERIFICATION", + "display": { + "label": "Verification", + "image": "" + }, + "types": [ + { + "type": "NEXT", + "meta": { + "actionType": "VERIFICATION" + }, + "display": { + "label": "Verify OTP", + "image": "https://www.svgrepo.com/show/497630/verify.svg", + "defaultVariant": "PRIMARY" + } + } + ] + }, + { + "category": "ONBOARDING", + "display": { + "label": "Onboarding", + "image": "" + }, + "types": [ + { + "type": "EXECUTOR", + "executors": [ + { + "name": "PasswordOnboarder" + } + ], + "display": { + "label": "Onboard Password", + "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" + } + } + ] + }, + { + "category": "SOCIAL", + "display": { + "label": "Social", + "image": "" + }, + "types": [ + { + "type": "EXECUTOR", + "executors": [ + { + "name": "GoogleOIDCAuthenticator", + "meta": { + "idp": "Google" + } + } + ], + "display": { + "label": "Continue with Google Sign Up", + "image": "https://www.svgrepo.com/show/475656/google-color.svg", + "defaultVariant": "SOCIAL" + } + } + ] + } +] diff --git a/features/admin.flow-builder-core.v1/data/components.json b/features/admin.flow-builder-core.v1/data/components.json index 1a71ddfc4bf..2881e6cf3e0 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" + } } }, { @@ -37,16 +31,13 @@ }, "config": { "field": { + "name": "password", "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 +53,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 +73,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 +93,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 +113,32 @@ "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" + } + } + }, + { + "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": "", + "name": "otp", + "label": "OTP", + "required": false, + "placeholder": "" + } } }, { @@ -157,18 +153,15 @@ }, "config": { "field": { - "className": "wso2is-input checkbox", "hint": "", "label": "Checkbox", - "required": false, - "defaultValue": "" - }, - "styles": {} + "required": false + } } }, { "category": "FIELD", - "type": "CHOICE", + "type": "INPUT", "version": "0.1.0", "deprecated": false, "display": { @@ -177,7 +170,6 @@ }, "config": { "field": { - "className": "wso2is-input choice", "hint": "", "label": "Choice", "required": false, @@ -199,8 +191,7 @@ "value": "option3" } ] - }, - "styles": {} + } } }, { @@ -227,11 +218,7 @@ "config": { "field": { "type": "submit", - "className": "wso2is-button primary", - "text": "Primary Button" - }, - "styles": { - "width": "100%" + "text": "Button" } } }, @@ -248,11 +235,7 @@ "config": { "field": { "type": "button", - "className": "wso2is-button secondary", "text": "Secondary Button" - }, - "styles": { - "width": "100%" } } }, @@ -269,11 +252,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 +298,7 @@ }, "config": { "field": { - "className": "wso2is-typography h1", "text": "Heading - H1" - }, - "styles": { - "textAlign": "center" } } }, @@ -322,11 +314,7 @@ }, "config": { "field": { - "className": "wso2is-typography h2", "text": "Heading - H2" - }, - "styles": { - "textAlign": "center" } } }, @@ -342,11 +330,7 @@ }, "config": { "field": { - "className": "wso2is-typography h3", "text": "Heading - H3" - }, - "styles": { - "textAlign": "center" } } }, @@ -362,11 +346,7 @@ }, "config": { "field": { - "className": "wso2is-typography h4", "text": "Heading - H4" - }, - "styles": { - "textAlign": "center" } } }, @@ -382,11 +362,7 @@ }, "config": { "field": { - "className": "wso2is-typography h5", "text": "Heading - H5" - }, - "styles": { - "textAlign": "center" } } }, @@ -402,11 +378,7 @@ }, "config": { "field": { - "className": "wso2is-typography h6", "text": "Heading - H6" - }, - "styles": { - "textAlign": "center" } } }, @@ -422,13 +394,7 @@ }, "config": { "field": { - "className": "wso2is-typography body1", "text": "Body Text" - }, - "styles": { - "fontSize": "14px", - "fontWeight": "400", - "textAlign": "center" } } }, @@ -444,13 +410,7 @@ }, "config": { "field": { - "className": "wso2is-typography body2", "text": "Body Text - Muted" - }, - "styles": { - "fontSize": "12px", - "fontWeight": "400", - "textAlign": "center" } } } @@ -467,11 +427,7 @@ }, "config": { "field": { - "className": "wso2is-rich-text", "text": "

Rich Text

" - }, - "styles": { - "textAlign": "center" } } }, @@ -498,10 +454,8 @@ }, "config": { "field": { - "className": "wso2is-divider horizontal", "text": "Or" - }, - "styles": {} + } } }, { @@ -516,10 +470,8 @@ }, "config": { "field": { - "className": "wso2is-divider vertical", "text": "Or" - }, - "styles": {} + } } } ] @@ -547,12 +499,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/widgets.json b/features/admin.flow-builder-core.v1/data/widgets.json index 4c4a7df8997..288fa6f872e 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,201 @@ "image": "https://www.svgrepo.com/show/368868/otp.svg" }, "config": { - "field": { - "className": "wso2is-email-otp" - }, - "styles": {} + "field": {} + }, + "flow": { + "nodes": [ + { + "id": "flow-node-{{STEP_NUMBER}}", + "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": [ + "{{NEXT_STEP_ID}}" + ] + }, + { + "id": "flow-action-go-back-er212kfl", + "action": { + "type": "PREVIOUS" + }, + "previous": [ + "{{PREVIOUS_STEP_ID}}" + ] + } + ] + }, + { + "id": "flow-node-{{STEP_NUMBER}}", + "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": [ + "{{NEXT_STEP_ID}}" + ] + }, + { + "id": "flow-action-go-back-yt5g5t", + "action": { + "type": "PREVIOUS" + }, + "previous": [ + "{{PREVIOUS_STEP_ID}}" + ] + } + ] + } + ], + "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" + } + } + } + ] } }, { @@ -40,10 +230,7 @@ "image": "https://www.svgrepo.com/show/381137/transaction-password-otp-verification-code-security.svg" }, "config": { - "field": { - "className": "wso2is-sms-otp" - }, - "styles": {} + "field": {} } } ] diff --git a/features/admin.flow-builder-core.v1/models/actions.ts b/features/admin.flow-builder-core.v1/models/actions.ts index 40a58e12b01..89c7ad13382 100644 --- a/features/admin.flow-builder-core.v1/models/actions.ts +++ b/features/admin.flow-builder-core.v1/models/actions.ts @@ -16,8 +16,43 @@ * under the License. */ +import { BaseDisplay } from "./base"; + +export interface Action { + category: ActionCategories; + display: BaseDisplay; + types: ActionType[]; +} + +export interface Executor { + name: string; + meta: Record; +} + +export interface ActionType { + type: ActionTypes; + display: BaseDisplay; + name?: string; + executors: Executor[]; + meta?: Record; +} + +export type Actions = Action[]; + +// TODO: Re-evaluate the following enums +export enum ActionCategories { + Navigation = "NAVIGATION", + Verification = "VERIFICATION", + CredentialOnboarding = "CREDENTIAL_ONBOARDING", + Executor = "SOCIAL" +} + export enum ActionTypes { Next = "NEXT", Previous = "PREVIOUS", - Submit = "SUBMIT" + Executor = "EXECUTOR" +} + +export enum ActionVariants { + SOCIAL = "SOCIAL" } 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..3e1fd205ffc --- /dev/null +++ b/features/admin.flow-builder-core.v1/models/api.ts @@ -0,0 +1,66 @@ +/** + * 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 as CoreElement } 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; + elements: string[]; +} + +export type Element = Omit; + +export interface Payload { + flow: Flow; + nodes: Node[]; + blocks: Block[]; + elements: Element[]; +} 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/component.ts b/features/admin.flow-builder-core.v1/models/component.ts index 176fce7d9dd..b45eb98e848 100644 --- a/features/admin.flow-builder-core.v1/models/component.ts +++ b/features/admin.flow-builder-core.v1/models/component.ts @@ -40,11 +40,13 @@ export enum InputVariants { Telephone = "TELEPHONE", Number = "NUMBER", Checkbox = "CHECKBOX", + OTP = "OTP" } export enum ButtonVariants { Primary = "PRIMARY", Secondary = "SECONDARY", + Social = "SOCIAL", Text = "TEXT" } 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/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 new file mode 100644 index 00000000000..a4a5e3ae33e --- /dev/null +++ b/features/admin.flow-builder-core.v1/utils/transform-flow.ts @@ -0,0 +1,254 @@ +/** + * 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 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, + Block as PayloadBlock, + 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", "deprecated", "meta" ]; + +const groupNodesIntoPages = (nodes: any[], edges: any[]): any[] => { + const nodePages: Record = {}; + const visitedNodes: Set = new Set(); + + const traverseNodes = (nodeId: string, pageId: string) => { + if (visitedNodes.has(nodeId)) return; + visitedNodes.add(nodeId); + + if (!nodePages[pageId]) { + nodePages[pageId] = []; + } + nodePages[pageId].push(nodeId); + + const connectedEdges: Edge[] = edges.filter((edge: any) => edge.source === nodeId); + + connectedEdges.forEach((edge: any) => { + const nextNodeId: string = edge.target; + + traverseNodes(nextNodeId, pageId + 1); + }); + }; + + nodes.forEach((node: any) => { + if (!visitedNodes.has(node.id)) { + const pageId: string = `flow-page-${Object.keys(nodePages).length + 1}`; + + traverseNodes(node.id, pageId); + } + }); + + return Object.keys(nodePages).map((pageId: string) => ({ + id: pageId, + nodes: nodePages[pageId] + })); +}; + +const transformFlow = (flowState: any): Payload => { + const { nodes: flowNodes, edges: flowEdges } = flowState; + + const payload: Payload = { + blocks: [], + elements: [], + flow: { + pages: [] + }, + nodes: [] + }; + + const nodeNavigationMap: Record = {}; + + flowEdges.forEach((edge: any) => { + nodeNavigationMap[edge.sourceHandle.replace("-NEXT", "").replace("-PREVIOUS", "")] = [ edge.target ]; + }); + + 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]; + } + } + }) + .flat() + .filter(Boolean); + + let _action: any = { + action: action.meta, + id: action.id + }; + + 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") { + // 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( + (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: { + ...(_action?.action?.meta || {}), + actionType: "CREDENTIAL_ONBOARDING" + } + } + }; + } + } else { + // 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) { + set(_action, "action.type", ActionTypes.Next); + } + + return _action; + }); + + let currentBlock: PayloadBlock | null = null; + const nodeElements: string[] = []; + + // Identify the last ACTION with type "submit" + const lastSubmitActionIndex: number = node.data?.components + ?.map((component: Element, index: number) => ({ component, index })) + .reverse() + .find( + ({ component }: { component: Element }) => + 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 = { + elements: [ component.id ], + id: `flow-block-${payload.blocks.length + 1}` + }; + payload.blocks.push(currentBlock); + nodeElements.push(currentBlock.id); + } else { + nodeElements.push(component.id); + } + } + }); + + 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 PayloadElement[]) + ); + }); + + // Add `next: ["COMPLETE"] to the last nodes' actions + // TODO: Improve. + const lastNode: PayloadNode = payload.nodes[payload.nodes.length - 1]; + + lastNode.actions.forEach((action: PayloadAction) => { + if (action.action?.type === ActionTypes.Next) { + action.next = [ "COMPLETE" ]; + } + }); + + payload.flow.pages = groupNodesIntoPages(flowNodes, flowEdges); + + return payload; +}; + +export default transformFlow; 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..e2505202048 --- /dev/null +++ b/features/admin.registration-flow-builder.v1/api/configure-registration-flow.ts @@ -0,0 +1,79 @@ +/** + * 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"; + +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) => { + // 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/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..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 @@ -16,31 +16,25 @@ * under the License. */ -import { FieldKey, FieldValue, Properties } from "@wso2is/admin.flow-builder-core.v1/models/base"; +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 { 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 React, { FunctionComponent, ReactElement } from "react"; +import isEmpty from "lodash-es/isEmpty"; +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"; /** * 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; -} +export type ElementPropertiesPropsInterface = CommonElementPropertiesPropsInterface & IdentifiableComponentInterface; /** * Factory to generate the property configurator for the given registration flow element. @@ -51,35 +45,75 @@ export interface ElementPropertiesPropsInterface extends IdentifiableComponentIn const ElementProperties: FunctionComponent = ({ properties, element, - onChange + 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 = () => { - return Object.entries(properties).map(([ key, value ]: [FieldKey, FieldValue]) => ( - - )); + const hasVariants: boolean = !isEmpty(element?.variants); + + return ( + <> + { hasVariants && ( + variant.variant } + renderInput={ (params: AutocompleteRenderInputParams) => ( + + ) } + value={ selectedVariant } + onChange={ (_: ChangeEvent, variant: Element) => { + onVariantChange(variant?.variant); + } } + /> + ) } + { Object.entries(properties).map(([ key, value ]: [FieldKey, FieldValue]) => ( + + )) } + + ); }; switch (element.category) { case ElementCategories.Field: + if (element.variant === InputVariants.Password) { + return renderElementPropertyFactory(); + } + return ( <> { renderElementPropertyFactory() } ); + case ElementCategories.Action: + return ( + <> + + { renderElementPropertyFactory() } + + ); default: return <>{ renderElementPropertyFactory() }; } diff --git a/features/admin.registration-flow-builder.v1/components/element-property-panel/element-property-factory.tsx b/features/admin.registration-flow-builder.v1/components/element-property-panel/element-property-factory.tsx index 785ad8d523a..1a805b9674e 100644 --- a/features/admin.registration-flow-builder.v1/components/element-property-panel/element-property-factory.tsx +++ b/features/admin.registration-flow-builder.v1/components/element-property-panel/element-property-factory.tsx @@ -41,11 +41,10 @@ export interface ElementPropertyFactoryPropsInterface extends IdentifiableCompon /** * 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.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..4cf60854ddf --- /dev/null +++ b/features/admin.registration-flow-builder.v1/components/element-property-panel/extended-properties/button-extended-properties.scss @@ -0,0 +1,47 @@ +.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. + // 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; + } + } + + &.selected { + border: 1px solid var(--oxygen-palette-primary-main); + } + } + + .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 dbf3010da19..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 @@ -16,19 +16,30 @@ * under the License. */ -import FormControl from "@oxygen-ui/react/FormControl"; -import Select from "@oxygen-ui/react/Select"; +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"; +import { + CommonElementPropertiesPropsInterface +} from "@wso2is/admin.flow-builder-core.v1/components/element-property-panel/element-properties"; // 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, useState } from "react"; -import { RegistrationFlowActionTypes } from "../../../models/actions"; +import classNames from "classnames"; +import isEqual from "lodash-es/isEqual"; +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} */ -export type ButtonExtendedPropertiesPropsInterface = CommonComponentPropertyFactoryPropsInterface & +export type ButtonExtendedPropertiesPropsInterface = CommonElementPropertiesPropsInterface & IdentifiableComponentInterface; /** @@ -38,22 +49,89 @@ 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, + onVariantChange }: ButtonExtendedPropertiesPropsInterface): ReactElement => { - const [ selectedActionType ] = useState(null); + const { data: actions } = useGetRegistrationFlowCoreActions(); + const { lastInteractedElement, setLastInteractedElement } = useAuthenticationFlowBuilderCore(); return ( - - - - + +
+ Action Type + { actions?.map((action: Action, index: number) => ( + + + { action?.display?.label } + + + { action.types?.map((actionType: ActionType, typeIndex: number) => ( + { + onVariantChange(actionType?.display?.defaultVariant); + + onChange( + "meta", + { + executors: actionType?.executors, + meta: actionType?.meta, + name: actionType?.name, + type: actionType?.type + }, + element + ); + + setLastInteractedElement({ + ...lastInteractedElement, + meta: { + executors: actionType?.executors, + meta: actionType?.meta, + name: actionType?.name, + type: actionType?.type + }, + variant: actionType?.display?.defaultVariant + }); + } } + > + + + + + + { 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 fcadefc27c2..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 @@ -16,21 +16,21 @@ * 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 { + 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, 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"; /** * Props interface of {@link FieldExtendedProperties} */ -export type FieldExtendedPropertiesPropsInterface = CommonComponentPropertyFactoryPropsInterface & +export type FieldExtendedPropertiesPropsInterface = CommonElementPropertiesPropsInterface & IdentifiableComponentInterface; /** @@ -45,34 +45,31 @@ const FieldExtendedProperties: FunctionComponent { const { data: attributes } = useGetSupportedProfileAttributes(); - const [ selectedAttribute, setSelectedAttribute ] = useState(null); + const [ _, setSelectedAttribute ] = useState(null); + + const selectedValue: Attribute = useMemo(() => { + return attributes?.find( + (attribute: Attribute) => attribute?.claimURI === element.config.field.name + ); + }, [ element.config.field.name, attributes ]); return ( - - - + setSelectedAttribute( + attributes?.find((attribute: Attribute) => attribute?.claimURI === attribute?.claimURI) + ); + } } + /> ); }; 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..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 @@ -17,19 +17,21 @@ */ 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 { 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"; import useGetRegistrationFlowBuilderElements from "../api/use-get-registration-flow-builder-elements"; import RegistrationFlowBuilderProvider from "../providers/registration-flow-builder-provider"; /** * Props interface of {@link RegistrationFlowBuilder} */ -export type RegistrationFlowBuilderPropsInterface = IdentifiableComponentInterface & HTMLAttributes; +export type RegistrationFlowBuilderPropsInterface = IdentifiableComponentInterface; /** * Entry point for the registration flow builder. @@ -43,13 +45,28 @@ const RegistrationFlowBuilder: FunctionComponent { const { data: elements } = useGetRegistrationFlowBuilderElements(); + const handleFlowSubmit = (payload: Payload) => { + configureRegistrationFlow(payload) + .then(() => { + // Handle success. + }) + .catch(() => { + // Handle error. + }); + }; + return ( - + ); 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.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 @@ +[] diff --git a/features/admin.flow-builder-core.v1/data/payload.json b/features/admin.registration-flow-builder.v1/data/payload.json similarity index 65% rename from features/admin.flow-builder-core.v1/data/payload.json rename to features/admin.registration-flow-builder.v1/data/payload.json index 1ce60150c66..35d5f64492d 100644 --- a/features/admin.flow-builder-core.v1/data/payload.json +++ b/features/admin.registration-flow-builder.v1/data/payload.json @@ -36,7 +36,12 @@ { "id": "flow-action-password-onboarder-p563u9Yn", "action": { - "type": "NEXT" + "type": "EXECUTOR", + "executors": [ + { + "name": "PasswordOnboarder" + } + ] }, "next": [ "flow-node-2" @@ -45,7 +50,12 @@ { "id": "flow-action-email-otp-verifier-5t8uJ6D3", "action": { - "type": "NEXT" + "type": "EXECUTOR", + "executors": [ + { + "name": "EmailOTPVerifier" + } + ] }, "next": [ "flow-node-3" @@ -55,7 +65,14 @@ "id": "flow-action-google-sign-up-Rt8uJ6D3", "action": { "type": "EXECUTOR", - "name": "GoogleSignUp" + "executors": [ + { + "name": "GoogleOIDCAuthenticator", + "meta": { + "idp": "Google" + } + } + ] }, "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" + ] } ] } @@ -171,8 +195,7 @@ "blocks": [ { "id": "flow-block-attributes-g55dfGuK", - "nodes": [ - "flow-field-email-Rt8uJ6D3", + "elements": [ "flow-field-username-F6D3t8uJ", "flow-field-first-name-r7u4F1GG", "flow-field-last-name-r7u4F1GG", @@ -184,21 +207,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" @@ -206,7 +229,7 @@ }, { "id": "flow-block-attributes-er203owe", - "nodes": [ + "elements": [ "flow-field-nic-RR342gr", "flow-action-done-5t8uJ6D3" ] @@ -221,11 +244,7 @@ "variant": "H3", "config": { "field": { - "className": "wso2is-typography h3", "text": "Register Account" - }, - "styles": { - "textAlign": "center" } } }, @@ -238,18 +257,12 @@ "config": { "field": { "type": "text", - "name": "username", - "className": "wso2is-input text", + "name": "http://wso2.org/claims/username", "hint": "", "label": "Username", "required": true, - "multiline": false, - "placeholder": "Enter your username", - "defaultValue": "", - "minLength": 3, - "maxLength": 50 - }, - "styles": {} + "placeholder": "Enter your username" + } } }, { @@ -261,18 +274,12 @@ "config": { "field": { "type": "text", - "name": "firstName", - "className": "wso2is-input text", + "name": "http://wso2.org/claims/givenname", "hint": "", "label": "First Name", "required": true, - "multiline": false, - "placeholder": "Enter your first name", - "defaultValue": "", - "minLength": 3, - "maxLength": 50 - }, - "styles": {} + "placeholder": "Enter your first name" + } } }, { @@ -284,18 +291,12 @@ "config": { "field": { "type": "text", - "name": "lastName", - "className": "wso2is-input text", + "name": "http://wso2.org/claims/lastname", "hint": "", "label": "Last Name", "required": true, - "multiline": false, - "placeholder": "Enter your last name", - "defaultValue": "", - "minLength": 3, - "maxLength": 50 - }, - "styles": {} + "placeholder": "Enter your last name" + } } }, { @@ -307,16 +308,12 @@ "config": { "field": { "type": "date", - "name": "dob", - "className": "wso2is-input date", + "name": "http://wso2.org/claims/dob", "hint": "", - "label": "Birth Date", + "label": "Date of Birth", "required": false, - "multiline": false, - "placeholder": "Enter your birth date", - "defaultValue": "" - }, - "styles": {} + "placeholder": "Enter your date of birth" + } } }, { @@ -328,11 +325,7 @@ "config": { "field": { "type": "submit", - "className": "wso2is-button", "text": "Continue with Password" - }, - "styles": { - "width": "100%" } } }, @@ -344,10 +337,8 @@ "variant": "HORIZONTAL", "config": { "field": { - "className": "wso2is-divider-horizontal", "text": "Or" - }, - "styles": {} + } } }, { @@ -359,13 +350,8 @@ "variant": "PRIMARY", "config": { "field": { - "action": "EmailOTPVerifier", "type": "submit", - "className": "wso2is-button", - "label": "Continue with Email OTP" - }, - "styles": { - "width": "100%" + "text": "Continue with Email OTP" } } }, @@ -377,10 +363,8 @@ "variant": "HORIZONTAL", "config": { "field": { - "className": "wso2is-divider-horizontal", "text": "Or" - }, - "styles": {} + } } }, { @@ -389,16 +373,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%" + "text": "Continue with Google Sign Up" } } }, @@ -410,11 +389,7 @@ "variant": "H3", "config": { "field": { - "className": "wso2is-typography h3", "text": "Enter Password" - }, - "styles": { - "textAlign": "center" } } }, @@ -428,13 +403,107 @@ "field": { "type": "password", "name": "password", - "className": "wso2is-input password", "hint": "", "label": "Password", "required": true, "multiline": false, "placeholder": "Enter your password", - "defaultValue": "" + "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": {} } @@ -449,15 +518,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,27 +534,19 @@ "config": { "field": { "type": "submit", - "className": "wso2is-button", "text": "Next" - }, - "styles": { - "width": "100%" } } }, { - "id": "flow-display-header-hj6t4D3", + "id": "flow-display-header-rg6pwt0", "category": "DISPLAY", "type": "TYPOGRAPHY", "version": "0.1.0", "variant": "H3", "config": { "field": { - "className": "wso2is-typography h3", "text": "Enter Email" - }, - "styles": { - "textAlign": "center" } } }, @@ -502,16 +559,12 @@ "config": { "field": { "type": "email", - "name": "email", - "className": "wso2is-input email", + "name": "http://wso2.org/claims/emailaddress", "hint": "", "label": "Email", "required": true, - "multiline": false, - "placeholder": "Enter your email address", - "defaultValue": "" - }, - "styles": {} + "placeholder": "Enter your email address" + } } }, { @@ -523,11 +576,7 @@ "config": { "field": { "type": "submit", - "className": "wso2is-button", "text": "Continue" - }, - "styles": { - "width": "100%" } } }, @@ -540,11 +589,7 @@ "config": { "field": { "type": "submit", - "className": "wso2is-button", "text": "Go back" - }, - "styles": { - "width": "100%" } } }, @@ -556,11 +601,7 @@ "variant": "H3", "config": { "field": { - "className": "wso2is-typography h3", "text": "OTP Verification" - }, - "styles": { - "textAlign": "center" } } }, @@ -573,16 +614,12 @@ "config": { "field": { "type": "text", - "name": "otp", - "className": "wso2is-input otp", + "name": "email-otp", "hint": "", "label": "OTP", "required": true, - "multiline": false, - "placeholder": "Enter the OTP", - "defaultValue": "" - }, - "styles": {} + "placeholder": "Enter the OTP" + } } }, { @@ -594,11 +631,7 @@ "config": { "field": { "type": "submit", - "className": "wso2is-button", "text": "Continue" - }, - "styles": { - "width": "100%" } } }, @@ -611,11 +644,7 @@ "config": { "field": { "type": "submit", - "className": "wso2is-button", "text": "Go back" - }, - "styles": { - "width": "100%" } } }, @@ -627,11 +656,7 @@ "variant": "H3", "config": { "field": { - "className": "wso2is-typography h3", "text": "Enter Personal Details" - }, - "styles": { - "textAlign": "center" } } }, @@ -644,16 +669,12 @@ "config": { "field": { "type": "text", - "name": "nic", - "className": "wso2is-text-input", + "name": "http://wso2.org/claims/im", "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 +687,7 @@ "config": { "field": { "type": "submit", - "className": "wso2is-button", - "label": "Done" - }, - "styles": { - "width": "100%" + "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; +} 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); } } }