null}
+ onChange={onChange}
+ onPlus={() => null}
+ inputName={props.name}
+ inputAriaLabel="number input"
+ minusBtnAriaLabel="minus"
+ plusBtnAriaLabel="plus"
+ />
+ );
+};
+
+export default NumberPicker;
diff --git a/src/smart-components/workspaces/create-workspace/Review.tsx b/src/smart-components/workspaces/create-workspace/Review.tsx
new file mode 100644
index 000000000..e8312d2ed
--- /dev/null
+++ b/src/smart-components/workspaces/create-workspace/Review.tsx
@@ -0,0 +1,68 @@
+import React from 'react';
+import { useFlag } from '@unleash/proxy-client-react';
+import useFormApi from '@data-driven-forms/react-form-renderer/use-form-api';
+import { Title, DescriptionListGroup, DescriptionList, DescriptionListTerm, DescriptionListDescription, Text } from '@patternfly/react-core';
+import { useIntl } from 'react-intl';
+import { BUNDLES, WORKSPACE_ACCOUNT, WORKSPACE_DESCRIPTION, WORKSPACE_FEATURES, WORKSPACE_NAME, WORKSPACE_PARENT } from './schema';
+import messages from '../../../Messages';
+
+const ReviewStep = () => {
+ const intl = useIntl();
+ const formOptions = useFormApi();
+ const values = formOptions.getState().values;
+ const enableBillingFeatures = useFlag('platform.rbac.workspaces-billing-features');
+
+ return (
+
+
+ {intl.formatMessage(messages.reviewNewWorkspace)}
+
+ {intl.formatMessage(messages.reviewWorkspaceDescription)}
+
+
+ {intl.formatMessage(messages.workspaceName)}
+ {values[WORKSPACE_NAME]}
+
+
+ {intl.formatMessage(messages.parentWorkspace)}
+ {values[WORKSPACE_PARENT].name}
+
+
+ {intl.formatMessage(messages.workspaceDetails)}
+ {values[WORKSPACE_DESCRIPTION] ?? '-'}
+
+ {enableBillingFeatures && (
+ <>
+
+ {intl.formatMessage(messages.billingAccount)}
+ {values[WORKSPACE_ACCOUNT]}
+
+
+ {intl.formatMessage(messages.availableFeatures)}
+
+ {values[WORKSPACE_FEATURES]?.length > 0 ? (
+ values[WORKSPACE_FEATURES].map((item: string) => {BUNDLES.find((bundle) => bundle.value === item)?.label})
+ ) : (
+ -
+ )}
+
+
+ >
+ )}
+ {values[WORKSPACE_FEATURES]?.length > 0 ? (
+
+ {intl.formatMessage(messages.earMarkOfFeatures)}
+
+ {values[WORKSPACE_FEATURES].map((item: string) => {
+ const bundle = BUNDLES.find((bundle) => bundle.value === item);
+ return {`${bundle?.label}: ${values[`ear-mark-${bundle?.value}-cores`] ?? 0} Cores`};
+ })}
+
+
+ ) : null}
+
+
+ );
+};
+
+export default ReviewStep;
diff --git a/src/smart-components/workspaces/create-workspace/SetDetails.tsx b/src/smart-components/workspaces/create-workspace/SetDetails.tsx
new file mode 100644
index 000000000..16fa98e75
--- /dev/null
+++ b/src/smart-components/workspaces/create-workspace/SetDetails.tsx
@@ -0,0 +1,138 @@
+import React, { useEffect, useState } from 'react';
+import { useIntl } from 'react-intl';
+import { useDispatch, useSelector } from 'react-redux';
+import { useFlag } from '@unleash/proxy-client-react';
+import {
+ Button,
+ FormGroup,
+ FormSelect,
+ FormSelectOption,
+ Grid,
+ GridItem,
+ MenuToggle,
+ MenuToggleElement,
+ Select,
+ SelectList,
+ SelectOption,
+ Skeleton,
+ Text,
+} from '@patternfly/react-core';
+import useFormApi from '@data-driven-forms/react-form-renderer/use-form-api';
+import { WORKSPACE_ACCOUNT, WORKSPACE_PARENT } from './schema';
+import { RBACStore } from '../../../redux/store';
+import { fetchWorkspaces } from '../../../redux/actions/workspaces-actions';
+import messages from '../../../Messages';
+import InputHelpPopover from '../../../presentational-components/InputHelpPopover';
+
+const SetDetails = () => {
+ const intl = useIntl();
+ const dispatch = useDispatch();
+ const enableBillingFeatures = useFlag('platform.rbac.workspaces-billing-features');
+ const formOptions = useFormApi();
+ const values = formOptions.getState().values;
+ const [isOpen, setIsOpen] = useState(false);
+ const { isLoading, workspaces } = useSelector((state: RBACStore) => state.workspacesReducer);
+
+ useEffect(() => {
+ dispatch(fetchWorkspaces());
+ }, [dispatch]);
+
+ useEffect(() => {
+ !values[WORKSPACE_PARENT] &&
+ formOptions.change(
+ WORKSPACE_PARENT,
+ workspaces.find((workspace) => workspace.parent_id === null)
+ );
+ }, [workspaces.length]);
+
+ const toggle = (toggleRef: React.Ref) => (
+ setIsOpen(!isOpen)} isExpanded={isOpen}>
+ {values[WORKSPACE_PARENT]?.name}
+
+ );
+
+ return (
+
+
+
+ {intl.formatMessage(messages.workspaceParentHelperText)}
+
+ >
+ }
+ field="parent workspace"
+ />
+ }
+ >
+ {isLoading ? (
+
+ ) : (
+
+ )}
+
+
+ {enableBillingFeatures && (
+
+
+ {intl.formatMessage(messages.workspaceBillingAccountHelperText)}
+
+ >
+ }
+ field="billing features"
+ />
+ }
+ >
+ formOptions.change(WORKSPACE_ACCOUNT, value)}
+ aria-label="Workspace billing account select"
+ ouiaId="SetDetails-billing-account-select"
+ >
+
+
+
+
+ )}
+
+ );
+};
+
+export default SetDetails;
diff --git a/src/smart-components/workspaces/create-workspace/SetEarMark.tsx b/src/smart-components/workspaces/create-workspace/SetEarMark.tsx
new file mode 100644
index 000000000..569c8870a
--- /dev/null
+++ b/src/smart-components/workspaces/create-workspace/SetEarMark.tsx
@@ -0,0 +1,42 @@
+import React from 'react';
+import { useIntl } from 'react-intl';
+import { Stack, StackItem, TextContent, Title, Text, NumberInput } from '@patternfly/react-core';
+import useFieldApi, { UseFieldApiConfig } from '@data-driven-forms/react-form-renderer/use-field-api';
+import useFormApi from '@data-driven-forms/react-form-renderer/use-form-api';
+import { WORKSPACE_ACCOUNT } from './schema';
+import messages from '../../../Messages';
+
+const SetEarMark = ({ feature, ...props }: UseFieldApiConfig) => {
+ const intl = useIntl();
+ const { input } = useFieldApi(props);
+ const formOptions = useFormApi();
+
+ return (
+
+
+
+ {intl.formatMessage(messages.setEarmark, { bundle: feature.label })}
+
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad
+ minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
+
+
+
+
+ {intl.formatMessage(messages.totalAccountAvailability, {
+ billingAccount: formOptions.getState().values[WORKSPACE_ACCOUNT] ?? 'XXX',
+ count: input.value || 0,
+ })}
+
+
+
+
+ {intl.formatMessage(messages.cores)}
+
+
+ );
+};
+
+export default SetEarMark;
diff --git a/src/smart-components/workspaces/create-workspace/schema.tsx b/src/smart-components/workspaces/create-workspace/schema.tsx
new file mode 100644
index 000000000..320b60806
--- /dev/null
+++ b/src/smart-components/workspaces/create-workspace/schema.tsx
@@ -0,0 +1,218 @@
+import React from 'react';
+import validatorTypes from '@data-driven-forms/react-form-renderer/validator-types';
+import InputHelpPopover from '../../../presentational-components/InputHelpPopover';
+import { locale } from '../../../AppEntry';
+import { createIntl, createIntlCache, FormattedMessage } from 'react-intl';
+import { componentTypes } from '@data-driven-forms/react-form-renderer';
+import { Workspace } from '../../../redux/reducers/workspaces-reducer';
+import providerMessages from '../../../locales/data.json';
+import messages from '../../../Messages';
+import { Button, Text } from '@patternfly/react-core';
+
+// hardcoded for now
+export const BUNDLES = [
+ {
+ label: 'OpenShift',
+ value: 'openshift',
+ },
+ {
+ label: 'RHEL',
+ value: 'rhel',
+ },
+ {
+ label: 'Ansible Lightspeed',
+ value: 'lightspeed',
+ },
+];
+
+export const WORKSPACE_NAME = 'workspace-name';
+export const WORKSPACE_DESCRIPTION = 'workspace-description';
+export const WORKSPACE_FEATURES = 'workspace-features';
+export const WORKSPACE_PARENT = 'workspace-parent';
+export const WORKSPACE_ACCOUNT = 'workspace-account';
+
+export interface CreateWorkspaceFormValues {
+ [WORKSPACE_NAME]: string;
+ [WORKSPACE_DESCRIPTION]: string;
+ [WORKSPACE_FEATURES]: string[];
+ [WORKSPACE_PARENT]: Workspace;
+ [WORKSPACE_ACCOUNT]: string;
+}
+
+export const schemaBuilder = (enableBillingFeatures: boolean) => {
+ const cache = createIntlCache();
+ const intl = createIntl({ locale, messages: providerMessages as any }, cache); // eslint-disable-line @typescript-eslint/no-explicit-any
+
+ return {
+ fields: [
+ {
+ component: 'wizard',
+ name: 'wizard',
+ isDynamic: true,
+ crossroads: ['workspace-features'],
+ 'data-ouia-component-id': 'create-workspace-wizard',
+ inModal: true,
+ showTitles: true,
+ title: intl.formatMessage(messages.createNewWorkspace),
+ fields: [
+ {
+ title: intl.formatMessage(messages.workspaceDetails),
+ showTitle: false,
+ name: 'details',
+ nextStep: () => (enableBillingFeatures ? 'select-features' : 'review'),
+ fields: [
+ {
+ name: 'details-title',
+ component: componentTypes.PLAIN_TEXT,
+ className: 'pf-v5-c-title pf-m-xl',
+ label: intl.formatMessage(messages.workspaceDetailsTitle),
+ },
+ {
+ name: 'details-description',
+ component: componentTypes.PLAIN_TEXT,
+ className: 'pf-v5-u-my-md',
+ label: intl.formatMessage(messages.workspaceDetailsDescription),
+ },
+ {
+ name: 'workspace-name',
+ component: componentTypes.TEXT_FIELD,
+ label: intl.formatMessage(messages.workspaceName),
+ isRequired: true,
+ FormGroupProps: {
+ labelIcon: (
+
+
+ {intl.formatMessage(messages.learnMore)}
+
+ ),
+ }}
+ />
+
+ }
+ field="workspace name"
+ />
+ ),
+ },
+ validate: [
+ {
+ type: validatorTypes.REQUIRED,
+ },
+ {
+ type: validatorTypes.MAX_LENGTH,
+ threshold: 150,
+ },
+ ],
+ },
+ {
+ name: 'workspace-details',
+ component: 'SetDetails',
+ fields: [
+ {
+ name: WORKSPACE_PARENT,
+ component: componentTypes.TEXT_FIELD,
+ isRequired: true,
+ hideField: true,
+ validate: [
+ {
+ type: validatorTypes.REQUIRED,
+ },
+ ],
+ },
+ {
+ name: WORKSPACE_ACCOUNT,
+ component: componentTypes.TEXT_FIELD,
+ isRequired: true,
+ hideField: true,
+ validate: [
+ {
+ type: validatorTypes.REQUIRED,
+ },
+ ],
+ },
+ ],
+ },
+ {
+ name: 'workspace-description',
+ component: componentTypes.TEXTAREA,
+ label: intl.formatMessage(messages.workspaceDescription),
+ FormGroupProps: {
+ labelIcon: (
+ {intl.formatMessage(messages.workspaceDescriptionMaxLength, { count: 255 })}}
+ field="workspace description"
+ />
+ ),
+ },
+ validate: [
+ {
+ type: validatorTypes.MAX_LENGTH,
+ threshold: 255,
+ },
+ ],
+ },
+ ],
+ },
+ {
+ title: intl.formatMessage(messages.selectFeatures),
+ name: 'select-features',
+ nextStep: ({ values }: { values: CreateWorkspaceFormValues }) => {
+ const selectedFeatures = values['workspace-features'] || [];
+ return selectedFeatures.length > 0 ? `ear-mark-${selectedFeatures[0]}` : 'review';
+ },
+ fields: [
+ {
+ name: 'features-description',
+ className: 'pf-v5-u-my-md',
+ component: componentTypes.PLAIN_TEXT,
+ label:
+ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.',
+ },
+ {
+ name: 'workspace-features',
+ className: 'pf-v5-u-my-sm',
+ component: componentTypes.CHECKBOX,
+ options: BUNDLES,
+ },
+ ],
+ },
+ ...BUNDLES.map((feature) => ({
+ name: `ear-mark-${feature.value}`,
+ title: feature.label,
+ showTitle: false,
+ substepOf: intl.formatMessage(messages.earMark),
+ nextStep: ({ values }: { values: CreateWorkspaceFormValues }) => {
+ const currIndex = values['workspace-features'].indexOf(feature.value);
+ return currIndex < values['workspace-features'].length - 1 ? `ear-mark-${values['workspace-features'][currIndex + 1]}` : 'review';
+ },
+ fields: [
+ {
+ component: 'SetEarMark',
+ name: `ear-mark-${feature.value}-cores`,
+ feature,
+ isRequired: true,
+ },
+ ],
+ })),
+ {
+ name: 'review',
+ title: intl.formatMessage(messages.review),
+ showTitle: false,
+ fields: [
+ {
+ component: 'Review',
+ name: 'review',
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ };
+};
diff --git a/src/utilities/pathnames.js b/src/utilities/pathnames.js
index 54036516a..092da5077 100644
--- a/src/utilities/pathnames.js
+++ b/src/utilities/pathnames.js
@@ -13,6 +13,11 @@ const pathnames = {
path: '/workspaces/*',
title: 'Workspaces',
},
+ 'create-workspace': {
+ link: '/workspaces/create-workspace',
+ path: 'create-workspace',
+ title: 'Create workspace',
+ },
'workspace-detail': {
link: '/workspaces/:workspaceId',
path: '/workspaces/:workspaceId/*',