From 35bb5b3c0e3cbe2d350d86e3b37f14f09b9eb04a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Tue, 3 Jun 2025 13:27:21 +0200 Subject: [PATCH 01/31] initialize --- public/locales/en.json | 4 +- .../ControlPlanes/ControlPlanesListMenu.tsx | 91 +++++++++++++++++++ .../ControlPlaneListWorkspaceGridTile.tsx | 3 + 3 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 src/components/ControlPlanes/ControlPlanesListMenu.tsx diff --git a/public/locales/en.json b/public/locales/en.json index 63ff429e..34096d1c 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -40,7 +40,9 @@ "tableHeaderUsage": "Usage" }, "ControlPlaneListToolbar": { - "buttonText": "Workspace" + "buttonText": "Workspace", + "deleteWorkspace": "Delete workspace", + "createNewManagedControlPlane": "Create new Managed Control Plane" }, "ControlPlaneListWorkspaceGridTile": { "deleteConfirmationDialog": "Workspace deletion triggered. The list will refresh automatically once completed.", diff --git a/src/components/ControlPlanes/ControlPlanesListMenu.tsx b/src/components/ControlPlanes/ControlPlanesListMenu.tsx new file mode 100644 index 00000000..38954f5c --- /dev/null +++ b/src/components/ControlPlanes/ControlPlanesListMenu.tsx @@ -0,0 +1,91 @@ +import { Button, Menu, MenuItem } from '@ui5/webcomponents-react'; +import { useToast } from '../../context/ToastContext.tsx'; +import { useRef, useState } from 'react'; +import '@ui5/webcomponents-icons/dist/copy'; +import '@ui5/webcomponents-icons/dist/accept'; +import { useMcp } from '../../lib/shared/McpContext.tsx'; +import { useTranslation } from 'react-i18next'; + +export const ControlPlanesListMenu = () => { + const popoverRef = useRef(null); + const [open, setOpen] = useState(false); + const { show } = useToast(); + const { t } = useTranslation(); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const handleOpenerClick = (e: any) => { + if (popoverRef.current) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const ref = popoverRef.current as any; + ref.opener = e.target; + setOpen((prev) => !prev); + } + }; + const mcp = useMcp(); + + return ( + <> + + { + if (event.detail.item.dataset.action === 'download') { + DownloadKubeconfig(mcp.kubeconfig, mcp.name); + return; + } + if (event.detail.item.dataset.action === 'copy') { + try { + navigator.clipboard.writeText(mcp.kubeconfig ?? ''); + show(t('CopyKubeconfigButton.copiedMessage')); + } catch (error) { + //TODO: handle error, show error to user + show(`${t('CopyKubeconfigButton.failedMessage')} ${error}`); + console.error(error); + } + } + + setOpen(false); + }} + > + + + + + ); +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function DownloadKubeconfig(config: any, displayName: string) { + const filename = 'kubeconfig-' + displayName + '.yaml'; + + try { + const file = new File([config], filename, { + type: 'application/yaml', + }); + + const link = document.createElement('a'); + const url = URL.createObjectURL(file); + + link.href = url; + link.download = file.name; + document.body.appendChild(link); + link.click(); + + document.body.removeChild(link); + window.URL.revokeObjectURL(url); + } catch (error) { + console.error(error); + } + // dynaLeaveAction(id); +} diff --git a/src/components/ControlPlanes/List/ControlPlaneListWorkspaceGridTile.tsx b/src/components/ControlPlanes/List/ControlPlaneListWorkspaceGridTile.tsx index 3cead5e8..a5b8152b 100644 --- a/src/components/ControlPlanes/List/ControlPlaneListWorkspaceGridTile.tsx +++ b/src/components/ControlPlanes/List/ControlPlaneListWorkspaceGridTile.tsx @@ -37,6 +37,8 @@ import { YamlViewButtonWithLoader } from '../../Yaml/YamlViewButtonWithLoader.ts import { IllustratedBanner } from '../../Ui/IllustratedBanner/IllustratedBanner.tsx'; import { useLink } from '../../../lib/shared/useLink.ts'; import IllustrationMessageType from '@ui5/webcomponents-fiori/dist/types/IllustrationMessageType.js'; +import CopyKubeconfigButton from '../CopyKubeconfigButton.tsx'; +import { ControlPlanesListMenu } from '../ControlPlanesListMenu.tsx'; interface Props { projectName: string; @@ -143,6 +145,7 @@ export function ControlPlaneListWorkspaceGridTile({ resourceName={workspaceName} resourceType={'workspaces'} /> + } From 200983425e7cf46f881741e2d9952a810b10bdb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Tue, 3 Jun 2025 14:26:10 +0200 Subject: [PATCH 02/31] fix --- .../ControlPlanes/ControlPlanesListMenu.tsx | 46 ++----------------- .../ControlPlaneListWorkspaceGridTile.tsx | 8 +++- .../IllustratedBanner/IllustratedBanner.tsx | 3 ++ 3 files changed, 14 insertions(+), 43 deletions(-) diff --git a/src/components/ControlPlanes/ControlPlanesListMenu.tsx b/src/components/ControlPlanes/ControlPlanesListMenu.tsx index 38954f5c..76a6fb46 100644 --- a/src/components/ControlPlanes/ControlPlanesListMenu.tsx +++ b/src/components/ControlPlanes/ControlPlanesListMenu.tsx @@ -1,15 +1,15 @@ import { Button, Menu, MenuItem } from '@ui5/webcomponents-react'; -import { useToast } from '../../context/ToastContext.tsx'; + import { useRef, useState } from 'react'; import '@ui5/webcomponents-icons/dist/copy'; import '@ui5/webcomponents-icons/dist/accept'; -import { useMcp } from '../../lib/shared/McpContext.tsx'; + import { useTranslation } from 'react-i18next'; export const ControlPlanesListMenu = () => { const popoverRef = useRef(null); const [open, setOpen] = useState(false); - const { show } = useToast(); + const { t } = useTranslation(); // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -21,7 +21,6 @@ export const ControlPlanesListMenu = () => { setOpen((prev) => !prev); } }; - const mcp = useMcp(); return ( <> @@ -30,19 +29,9 @@ export const ControlPlanesListMenu = () => { ref={popoverRef} open={open} onItemClick={(event) => { - if (event.detail.item.dataset.action === 'download') { - DownloadKubeconfig(mcp.kubeconfig, mcp.name); - return; + if (event.detail.item.dataset.action === 'newManagedControlPlane') { } - if (event.detail.item.dataset.action === 'copy') { - try { - navigator.clipboard.writeText(mcp.kubeconfig ?? ''); - show(t('CopyKubeconfigButton.copiedMessage')); - } catch (error) { - //TODO: handle error, show error to user - show(`${t('CopyKubeconfigButton.failedMessage')} ${error}`); - console.error(error); - } + if (event.detail.item.dataset.action === 'deleteWorkspace') { } setOpen(false); @@ -64,28 +53,3 @@ export const ControlPlanesListMenu = () => { ); }; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function DownloadKubeconfig(config: any, displayName: string) { - const filename = 'kubeconfig-' + displayName + '.yaml'; - - try { - const file = new File([config], filename, { - type: 'application/yaml', - }); - - const link = document.createElement('a'); - const url = URL.createObjectURL(file); - - link.href = url; - link.download = file.name; - document.body.appendChild(link); - link.click(); - - document.body.removeChild(link); - window.URL.revokeObjectURL(url); - } catch (error) { - console.error(error); - } - // dynaLeaveAction(id); -} diff --git a/src/components/ControlPlanes/List/ControlPlaneListWorkspaceGridTile.tsx b/src/components/ControlPlanes/List/ControlPlaneListWorkspaceGridTile.tsx index a5b8152b..a069827d 100644 --- a/src/components/ControlPlanes/List/ControlPlaneListWorkspaceGridTile.tsx +++ b/src/components/ControlPlanes/List/ControlPlaneListWorkspaceGridTile.tsx @@ -37,7 +37,7 @@ import { YamlViewButtonWithLoader } from '../../Yaml/YamlViewButtonWithLoader.ts import { IllustratedBanner } from '../../Ui/IllustratedBanner/IllustratedBanner.tsx'; import { useLink } from '../../../lib/shared/useLink.ts'; import IllustrationMessageType from '@ui5/webcomponents-fiori/dist/types/IllustrationMessageType.js'; -import CopyKubeconfigButton from '../CopyKubeconfigButton.tsx'; + import { ControlPlanesListMenu } from '../ControlPlanesListMenu.tsx'; interface Props { @@ -105,7 +105,6 @@ export function ControlPlaneListWorkspaceGridTile({ + {t('ControlPlaneListToolbar.createNewManagedControlPlane')} + + } /> ) : ( diff --git a/src/components/Ui/IllustratedBanner/IllustratedBanner.tsx b/src/components/Ui/IllustratedBanner/IllustratedBanner.tsx index 2121a736..fdb1e5c5 100644 --- a/src/components/Ui/IllustratedBanner/IllustratedBanner.tsx +++ b/src/components/Ui/IllustratedBanner/IllustratedBanner.tsx @@ -13,6 +13,7 @@ type InfoBannerProps = { buttonText: string; buttonIcon?: string; }; + button?: React.ReactElement; }; export const IllustratedBanner = ({ @@ -20,6 +21,7 @@ export const IllustratedBanner = ({ subtitle, illustrationName, help, + button, }: InfoBannerProps) => { return ( @@ -41,6 +43,7 @@ export const IllustratedBanner = ({ )} + {button} ); }; From eca41fb3a2fa5dbd2f02908e7522001e8e8e5666 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Wed, 4 Jun 2025 14:42:51 +0200 Subject: [PATCH 03/31] refactor --- public/locales/en.json | 4 +- .../ControlPlanes/ControlPlanesListMenu.tsx | 14 ++- .../ControlPlaneListWorkspaceGridTile.tsx | 31 +++-- .../CreateProjectWorkspaceDialog.module.css | 5 + .../Dialogs/CreateProjectWorkspaceDialog.tsx | 70 ++++++----- .../IllustratedBanner/IllustratedBanner.tsx | 2 +- ...eateManagedControlPlaneWizardContainer.tsx | 113 ++++++++++++++++++ 7 files changed, 189 insertions(+), 50 deletions(-) create mode 100644 src/components/Dialogs/CreateProjectWorkspaceDialog.module.css create mode 100644 src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx diff --git a/public/locales/en.json b/public/locales/en.json index 34096d1c..3aa569a0 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -81,8 +81,8 @@ "menuCopy": "Copy to clipboard" }, "IllustratedBanner": { - "titleMessage": "No ManagedControlPlane", - "subtitleMessage": "Create a ManagedControlPlane to get started", + "titleMessage": "You donโ€™t have any Managed Control Planes", + "subtitleMessage": "Create a new Managed Control Plane to get started", "helpButton": "Help" }, "IntelligentBreadcrumbs": { diff --git a/src/components/ControlPlanes/ControlPlanesListMenu.tsx b/src/components/ControlPlanes/ControlPlanesListMenu.tsx index 76a6fb46..2dbfacf8 100644 --- a/src/components/ControlPlanes/ControlPlanesListMenu.tsx +++ b/src/components/ControlPlanes/ControlPlanesListMenu.tsx @@ -1,12 +1,20 @@ import { Button, Menu, MenuItem } from '@ui5/webcomponents-react'; -import { useRef, useState } from 'react'; +import { Dispatch, FC, SetStateAction, useRef, useState } from 'react'; import '@ui5/webcomponents-icons/dist/copy'; import '@ui5/webcomponents-icons/dist/accept'; import { useTranslation } from 'react-i18next'; -export const ControlPlanesListMenu = () => { +type ControlPlanesListMenuProps = { + setDialogDeleteWsIsOpen: Dispatch>; + setIsCreateManagedControlPlaneWizardOpen: Dispatch>; +}; + +export const ControlPlanesListMenu: FC = ({ + setDialogDeleteWsIsOpen, + setIsCreateManagedControlPlaneWizardOpen, +}) => { const popoverRef = useRef(null); const [open, setOpen] = useState(false); @@ -30,8 +38,10 @@ export const ControlPlanesListMenu = () => { open={open} onItemClick={(event) => { if (event.detail.item.dataset.action === 'newManagedControlPlane') { + setIsCreateManagedControlPlaneWizardOpen(true); } if (event.detail.item.dataset.action === 'deleteWorkspace') { + setDialogDeleteWsIsOpen(true); } setOpen(false); diff --git a/src/components/ControlPlanes/List/ControlPlaneListWorkspaceGridTile.tsx b/src/components/ControlPlanes/List/ControlPlaneListWorkspaceGridTile.tsx index a069827d..455ec556 100644 --- a/src/components/ControlPlanes/List/ControlPlaneListWorkspaceGridTile.tsx +++ b/src/components/ControlPlanes/List/ControlPlaneListWorkspaceGridTile.tsx @@ -39,6 +39,7 @@ import { useLink } from '../../../lib/shared/useLink.ts'; import IllustrationMessageType from '@ui5/webcomponents-fiori/dist/types/IllustrationMessageType.js'; import { ControlPlanesListMenu } from '../ControlPlanesListMenu.tsx'; +import { CreateManagedControlPlaneWizardContainer } from '../../Wizards/CreateManagedControlPlaneWizardContainer.tsx'; interface Props { projectName: string; @@ -49,6 +50,10 @@ export function ControlPlaneListWorkspaceGridTile({ projectName, workspace, }: Props) { + const [ + isCreateManagedControlPlaneWizardOpen, + setIsCreateManagedControlPlaneWizardOpen, + ] = useState(false); const workspaceName = workspace.metadata.name; const workspaceDisplayName = workspace.metadata.annotations?.[DISPLAY_NAME_ANNOTATION] || ''; @@ -132,19 +137,17 @@ export function ControlPlaneListWorkspaceGridTile({ workspace={workspaceName} /> - } @@ -198,6 +207,10 @@ export function ControlPlaneListWorkspaceGridTile({ ); }} /> + ); } diff --git a/src/components/Dialogs/CreateProjectWorkspaceDialog.module.css b/src/components/Dialogs/CreateProjectWorkspaceDialog.module.css new file mode 100644 index 00000000..05707b43 --- /dev/null +++ b/src/components/Dialogs/CreateProjectWorkspaceDialog.module.css @@ -0,0 +1,5 @@ +.input { + width: 100%; + margin-bottom: 2rem; + max-width: 40ch; +} \ No newline at end of file diff --git a/src/components/Dialogs/CreateProjectWorkspaceDialog.tsx b/src/components/Dialogs/CreateProjectWorkspaceDialog.tsx index f0ac47ca..efd2c668 100644 --- a/src/components/Dialogs/CreateProjectWorkspaceDialog.tsx +++ b/src/components/Dialogs/CreateProjectWorkspaceDialog.tsx @@ -4,21 +4,20 @@ import { Dialog, Form, FormGroup, - FormItem, Input, Label, } from '@ui5/webcomponents-react'; import { Member } from '../../lib/api/types/shared/members'; import { ErrorDialog, ErrorDialogHandle } from '../Shared/ErrorMessageBox.tsx'; - +import styles from './CreateProjectWorkspaceDialog.module.css'; import { FormEvent, useState } from 'react'; import { KubectlInfoButton } from './KubectlCommandInfo/KubectlInfoButton.tsx'; import { KubectlCreateWorkspaceDialog } from './KubectlCommandInfo/KubectlCreateWorkspaceDialog.tsx'; import { KubectlCreateProjectDialog } from './KubectlCommandInfo/KubectlCreateProjectDialog.tsx'; import { EditMembers } from '../Members/EditMembers.tsx'; -// import { useFrontendConfig } from '../../context/FrontendConfigContext.tsx'; + import { useTranslation } from 'react-i18next'; import { CreateDialogProps } from './CreateWorkspaceDialogContainer.tsx'; @@ -79,7 +78,6 @@ export function CreateProjectWorkspaceDialog({ > + + } + /> + } + onClose={() => setIsOpen(false)} + > + ); +}; From b0d1067ac4b74ad9ad6a921ec19ae74497aa502c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Thu, 5 Jun 2025 15:45:09 +0200 Subject: [PATCH 04/31] fixes --- .../Dialogs/CreateProjectWorkspaceDialog.tsx | 2 +- ...eateManagedControlPlaneWizardContainer.tsx | 39 +++++++++++++++++-- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/src/components/Dialogs/CreateProjectWorkspaceDialog.tsx b/src/components/Dialogs/CreateProjectWorkspaceDialog.tsx index efd2c668..985c54cb 100644 --- a/src/components/Dialogs/CreateProjectWorkspaceDialog.tsx +++ b/src/components/Dialogs/CreateProjectWorkspaceDialog.tsx @@ -117,7 +117,7 @@ interface CreateProjectWorkspaceDialogContentProps { setValue: UseFormSetValue; } -function CreateProjectWorkspaceDialogContent({ +export function CreateProjectWorkspaceDialogContent({ members, register, errors, diff --git a/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx b/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx index e84a408d..0c090d8e 100644 --- a/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx +++ b/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx @@ -1,4 +1,4 @@ -import { FC, useCallback, useEffect, useRef } from 'react'; +import { FC, useCallback, useEffect, useRef, useState } from 'react'; import { useApiResourceMutation, useRevalidateApiResource, @@ -22,9 +22,16 @@ import { useForm } from 'react-hook-form'; import { validationSchemaProjectWorkspace } from '../../lib/api/validations/schemas.ts'; import { CreateProjectWorkspaceDialog, + CreateProjectWorkspaceDialogContent, OnCreatePayload, } from '../Dialogs/CreateProjectWorkspaceDialog.tsx'; -import { Bar, Button, Dialog } from '@ui5/webcomponents-react'; +import { + Bar, + Button, + Dialog, + Wizard, + WizardStep, +} from '@ui5/webcomponents-react'; import { KubectlInfoButton } from '../Dialogs/KubectlCommandInfo/KubectlInfoButton.tsx'; export type CreateDialogProps = { @@ -61,7 +68,7 @@ export const CreateManagedControlPlaneWizardContainer: FC< }); const { t } = useTranslation(); const { user } = useAuth(); - + const [selectedStep, useSelectedStep] = useState(1); const username = user?.email; const clearForm = useCallback(() => { @@ -108,6 +115,30 @@ export const CreateManagedControlPlaneWizardContainer: FC< /> } onClose={() => setIsOpen(false)} - > + > + + + + + + + ); }; From 98f87e613a8be35665eb2c83ccbf1eb6c17699bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Thu, 5 Jun 2025 16:02:58 +0200 Subject: [PATCH 05/31] fix --- src/components/Dialogs/CreateProjectWorkspaceDialog.tsx | 8 ++++---- .../Wizards/CreateManagedControlPlaneWizardContainer.tsx | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/Dialogs/CreateProjectWorkspaceDialog.tsx b/src/components/Dialogs/CreateProjectWorkspaceDialog.tsx index 985c54cb..cbf58148 100644 --- a/src/components/Dialogs/CreateProjectWorkspaceDialog.tsx +++ b/src/components/Dialogs/CreateProjectWorkspaceDialog.tsx @@ -89,7 +89,7 @@ export function CreateProjectWorkspaceDialog({ } onClose={() => setIsOpen(false)} > - ; errors: FieldErrors; setValue: UseFormSetValue; } -export function CreateProjectWorkspaceDialogContent({ +export function MetadataAndMembersForm({ members, register, errors, setValue, -}: CreateProjectWorkspaceDialogContentProps) { +}: MetadataAndMembersFormProps) { const { t } = useTranslation(); const setMembers = (members: Member[]) => { diff --git a/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx b/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx index 0c090d8e..c78606fc 100644 --- a/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx +++ b/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx @@ -22,7 +22,7 @@ import { useForm } from 'react-hook-form'; import { validationSchemaProjectWorkspace } from '../../lib/api/validations/schemas.ts'; import { CreateProjectWorkspaceDialog, - CreateProjectWorkspaceDialogContent, + MetadataAndMembersForm, OnCreatePayload, } from '../Dialogs/CreateProjectWorkspaceDialog.tsx'; import { @@ -124,7 +124,7 @@ export const CreateManagedControlPlaneWizardContainer: FC< selected={selectedStep === 1} data-step={'1'} > - Date: Fri, 6 Jun 2025 14:43:30 +0200 Subject: [PATCH 06/31] Update CreateManagedControlPlaneWizardContainer.tsx --- ...eateManagedControlPlaneWizardContainer.tsx | 106 ++++++++++++++---- 1 file changed, 83 insertions(+), 23 deletions(-) diff --git a/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx b/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx index c78606fc..f6caac36 100644 --- a/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx +++ b/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx @@ -4,10 +4,8 @@ import { useRevalidateApiResource, } from '../../lib/api/useApiResource'; import { ErrorDialogHandle } from '../Shared/ErrorMessageBox.tsx'; -import { APIError } from '../../lib/api/error'; import { - CreateWorkspace, CreateWorkspaceResource, CreateWorkspaceType, } from '../../lib/api/types/crate/createWorkspace'; @@ -20,19 +18,20 @@ import { useTranslation } from 'react-i18next'; import { zodResolver } from '@hookform/resolvers/zod'; import { useForm } from 'react-hook-form'; import { validationSchemaProjectWorkspace } from '../../lib/api/validations/schemas.ts'; -import { - CreateProjectWorkspaceDialog, - MetadataAndMembersForm, - OnCreatePayload, -} from '../Dialogs/CreateProjectWorkspaceDialog.tsx'; +import { MetadataAndMembersForm } from '../Dialogs/CreateProjectWorkspaceDialog.tsx'; import { Bar, Button, Dialog, + Grid, + List, + ListItemStandard, + Text, Wizard, WizardStep, } from '@ui5/webcomponents-react'; -import { KubectlInfoButton } from '../Dialogs/KubectlCommandInfo/KubectlInfoButton.tsx'; +import YamlViewer from '../Yaml/YamlViewer.tsx'; +import { stringify } from 'yaml'; export type CreateDialogProps = { name: string; @@ -55,7 +54,9 @@ export const CreateManagedControlPlaneWizardContainer: FC< handleSubmit, resetField, setValue, - formState: { errors }, + reset, + getValues, + formState: { errors, isValid }, watch, } = useForm({ resolver: zodResolver(validationSchemaProjectWorkspace), @@ -66,9 +67,15 @@ export const CreateManagedControlPlaneWizardContainer: FC< members: [], }, }); - const { t } = useTranslation(); + const resetFormAndClose = () => { + reset(); + setSelectedStep(1); + setIsOpen(false); + }; + console.log(errors); + // const { t } = useTranslation(); const { user } = useAuth(); - const [selectedStep, useSelectedStep] = useState(1); + const [selectedStep, setSelectedStep] = useState(1); const username = user?.email; const clearForm = useCallback(() => { @@ -87,15 +94,20 @@ export const CreateManagedControlPlaneWizardContainer: FC< clearForm(); } }, [resetField, setValue, username, isOpen, clearForm]); - const namespace = projectnameToNamespace(project); - const toast = useToast(); - - const { trigger } = useApiResourceMutation( - CreateWorkspaceResource(namespace), - ); - const revalidate = useRevalidateApiResource(ListWorkspaces(project)); - const errorDialogRef = useRef(null); + const onNextClick = () => { + console.log('test'); + handleSubmit( + () => { + console.log('valid'); + setSelectedStep(2); + }, + () => { + console.log('not valid'); + console.log(errors); + }, + )(); + }; return ( - + } @@ -123,6 +138,9 @@ export const CreateManagedControlPlaneWizardContainer: FC< disabled={false} selected={selectedStep === 1} data-step={'1'} + onClick={() => { + setSelectedStep(1); + }} > + onClick={() => { + setSelectedStep(1); + }} + > +

Summarize

+ +
+ + + + {' '} + {' '} + + +
+ + {getValues('members').map((member) => ( + + ))} + +
+
+ +
+
+
); From efb07fbbe82a6cd14af2ff9811ae883a42245b94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Mon, 9 Jun 2025 11:56:35 +0200 Subject: [PATCH 07/31] init --- ...eateManagedControlPlaneWizardContainer.tsx | 65 +++++++++++++----- .../types/crate/createManagedControlPlane.ts | 66 +++++++++++++++++++ 2 files changed, 115 insertions(+), 16 deletions(-) create mode 100644 src/lib/api/types/crate/createManagedControlPlane.ts diff --git a/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx b/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx index f6caac36..252b18fd 100644 --- a/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx +++ b/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx @@ -1,24 +1,16 @@ import { FC, useCallback, useEffect, useRef, useState } from 'react'; -import { - useApiResourceMutation, - useRevalidateApiResource, -} from '../../lib/api/useApiResource'; -import { ErrorDialogHandle } from '../Shared/ErrorMessageBox.tsx'; +import { useApiResourceMutation } from '../../lib/api/useApiResource'; -import { - CreateWorkspaceResource, - CreateWorkspaceType, -} from '../../lib/api/types/crate/createWorkspace'; -import { projectnameToNamespace } from '../../utils'; -import { ListWorkspaces } from '../../lib/api/types/crate/listWorkspaces'; -import { useToast } from '../../context/ToastContext.tsx'; import { useAuth } from '../../spaces/onboarding/auth/AuthContext.tsx'; import { Member, MemberRoles } from '../../lib/api/types/shared/members.ts'; -import { useTranslation } from 'react-i18next'; + import { zodResolver } from '@hookform/resolvers/zod'; import { useForm } from 'react-hook-form'; import { validationSchemaProjectWorkspace } from '../../lib/api/validations/schemas.ts'; -import { MetadataAndMembersForm } from '../Dialogs/CreateProjectWorkspaceDialog.tsx'; +import { + MetadataAndMembersForm, + OnCreatePayload, +} from '../Dialogs/CreateProjectWorkspaceDialog.tsx'; import { Bar, Button, @@ -26,12 +18,19 @@ import { Grid, List, ListItemStandard, - Text, Wizard, WizardStep, } from '@ui5/webcomponents-react'; import YamlViewer from '../Yaml/YamlViewer.tsx'; import { stringify } from 'yaml'; +import { APIError } from '../../lib/api/error.ts'; +import { + CreateManagedControlPlane, + CreateManagedControlPlaneResource, + CreateManagedControlPlaneType, +} from '../../lib/api/types/crate/createManagedControlPlane.ts'; +import { ErrorDialogHandle } from '../Shared/ErrorMessageBox.tsx'; +import { useToast } from '../../context/ToastContext.tsx'; export type CreateDialogProps = { name: string; @@ -67,6 +66,7 @@ export const CreateManagedControlPlaneWizardContainer: FC< members: [], }, }); + const errorDialogRef = useRef(null); const resetFormAndClose = () => { reset(); setSelectedStep(1); @@ -77,7 +77,7 @@ export const CreateManagedControlPlaneWizardContainer: FC< const { user } = useAuth(); const [selectedStep, setSelectedStep] = useState(1); const username = user?.email; - + const toast = useToast(); const clearForm = useCallback(() => { resetField('name'); resetField('chargingTarget'); @@ -94,6 +94,39 @@ export const CreateManagedControlPlaneWizardContainer: FC< clearForm(); } }, [resetField, setValue, username, isOpen, clearForm]); + const { trigger } = useApiResourceMutation( + CreateManagedControlPlaneResource(namespace), + ); + const handleCreateManagedControlPlane = async ({ + name, + displayName, + chargingTarget, + members, + }: OnCreatePayload): Promise => { + try { + await trigger( + CreateManagedControlPlane(name, namespace, { + displayName: displayName, + chargingTarget: chargingTarget, + members: members, + }), + ); + // await revalidate(); + setIsOpen(false); + toast.show('mcp created'); + return true; + } catch (e) { + console.error(e); + if (e instanceof APIError) { + if (errorDialogRef.current) { + errorDialogRef.current.showErrorDialog( + `${e.message}: ${JSON.stringify(e.info)}`, + ); + } + } + return false; + } + }; const onNextClick = () => { console.log('test'); diff --git a/src/lib/api/types/crate/createManagedControlPlane.ts b/src/lib/api/types/crate/createManagedControlPlane.ts new file mode 100644 index 00000000..293df9d1 --- /dev/null +++ b/src/lib/api/types/crate/createManagedControlPlane.ts @@ -0,0 +1,66 @@ +import { Resource } from '../resource'; +import { + CHARGING_TARGET_LABEL, + DISPLAY_NAME_ANNOTATION, +} from '../shared/keyNames'; +import { Member } from '../shared/members'; + +export interface CreateManagedControlPlaneType { + apiVersion: string; + kind: string; + metadata: { + name: string; + namespace: string; + annotations: { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + [annotation: string]: any; + }; + labels: { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + [label: string]: any; + }; + }; + spec: { + members: Member[]; + }; +} + +export const CreateManagedControlPlane = ( + name: string, + namespace: string, + optional?: { + displayName?: string; + chargingTarget?: string; + members?: Member[]; + }, +): CreateManagedControlPlaneType => { + return { + apiVersion: 'core.openmcp.cloud/v1alpha1', + kind: 'ControlPlane', + metadata: { + name: name, + namespace: namespace, + annotations: { + [DISPLAY_NAME_ANNOTATION]: optional?.displayName ?? '', + }, + labels: { + [CHARGING_TARGET_LABEL]: optional?.chargingTarget ?? '', + }, + }, + spec: { + members: optional?.members ?? [], + }, + }; +}; + +export const CreateManagedControlPlaneResource = ( + projectName: string, + workspaceName: string, +): Resource => { + return { + path: `/apis/core.openmcp.cloud/v1alpha1/namespaces/project-${projectName}--ws-${workspaceName}/managedcontrolplanes`, + method: 'POST', + jq: undefined, + body: undefined, + }; +}; From c036777bf84be0b4d3e21a035613dff1e8525b69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Mon, 9 Jun 2025 12:38:15 +0200 Subject: [PATCH 08/31] fixes --- .../ControlPlaneListWorkspaceGridTile.tsx | 2 + ...eateManagedControlPlaneWizardContainer.tsx | 40 +++++++++++-------- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/src/components/ControlPlanes/List/ControlPlaneListWorkspaceGridTile.tsx b/src/components/ControlPlanes/List/ControlPlaneListWorkspaceGridTile.tsx index 455ec556..71340410 100644 --- a/src/components/ControlPlanes/List/ControlPlaneListWorkspaceGridTile.tsx +++ b/src/components/ControlPlanes/List/ControlPlaneListWorkspaceGridTile.tsx @@ -210,6 +210,8 @@ export function ControlPlaneListWorkspaceGridTile({ ); diff --git a/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx b/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx index 252b18fd..a1ca32e8 100644 --- a/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx +++ b/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx @@ -42,12 +42,13 @@ export type CreateDialogProps = { type CreateManagedControlPlaneWizardContainerProps = { isOpen: boolean; setIsOpen: (isOpen: boolean) => void; - project?: string; + projectName?: string; + workspaceName?: string; }; export const CreateManagedControlPlaneWizardContainer: FC< CreateManagedControlPlaneWizardContainerProps -> = ({ isOpen, setIsOpen, project = '' }) => { +> = ({ isOpen, setIsOpen, projectName = '', workspaceName = '' }) => { const { register, handleSubmit, @@ -55,6 +56,7 @@ export const CreateManagedControlPlaneWizardContainer: FC< setValue, reset, getValues, + formState: { errors, isValid }, watch, } = useForm({ @@ -95,7 +97,7 @@ export const CreateManagedControlPlaneWizardContainer: FC< } }, [resetField, setValue, username, isOpen, clearForm]); const { trigger } = useApiResourceMutation( - CreateManagedControlPlaneResource(namespace), + CreateManagedControlPlaneResource(projectName, workspaceName), ); const handleCreateManagedControlPlane = async ({ name, @@ -105,7 +107,7 @@ export const CreateManagedControlPlaneWizardContainer: FC< }: OnCreatePayload): Promise => { try { await trigger( - CreateManagedControlPlane(name, namespace, { + CreateManagedControlPlane(name, workspaceName, { displayName: displayName, chargingTarget: chargingTarget, members: members, @@ -130,16 +132,22 @@ export const CreateManagedControlPlaneWizardContainer: FC< const onNextClick = () => { console.log('test'); - handleSubmit( - () => { - console.log('valid'); - setSelectedStep(2); - }, - () => { - console.log('not valid'); - console.log(errors); - }, - )(); + console.log(getValues()); + if (selectedStep === 1) { + handleSubmit( + () => { + console.log('valid'); + setSelectedStep(2); + }, + () => { + console.log('not valid'); + console.log(errors); + }, + )(); + } + if (selectedStep === 2) { + handleCreateManagedControlPlane(getValues()); + } }; return ( } @@ -189,7 +197,7 @@ export const CreateManagedControlPlaneWizardContainer: FC< selected={selectedStep === 2} data-step={'2'} onClick={() => { - setSelectedStep(1); + setSelectedStep(2); }} >

Summarize

From 1c71d7f50a1813f0008ba3b7608da6517ca719ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Tue, 10 Jun 2025 10:33:22 +0200 Subject: [PATCH 09/31] fixes --- .../CreateManagedControlPlaneWizardContainer.tsx | 4 +++- .../api/types/crate/createManagedControlPlane.ts | 16 +++++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx b/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx index a1ca32e8..e104e758 100644 --- a/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx +++ b/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx @@ -107,7 +107,7 @@ export const CreateManagedControlPlaneWizardContainer: FC< }: OnCreatePayload): Promise => { try { await trigger( - CreateManagedControlPlane(name, workspaceName, { + CreateManagedControlPlane(name, `${projectName}--ws-${workspaceName}`, { displayName: displayName, chargingTarget: chargingTarget, members: members, @@ -149,6 +149,8 @@ export const CreateManagedControlPlaneWizardContainer: FC< handleCreateManagedControlPlane(getValues()); } }; + console.log('selected'); + console.log(selectedStep); return ( { return { apiVersion: 'core.openmcp.cloud/v1alpha1', - kind: 'ControlPlane', + kind: 'ManagedControlPlane', metadata: { name: name, namespace: namespace, @@ -49,6 +49,16 @@ export const CreateManagedControlPlane = ( }, spec: { members: optional?.members ?? [], + // dataplane: { + // type: 'Gardener', + // gardener: { + // region: 'eu-west-1', + // }, + // }, + // crossplane: { + // enabled: false, + // version: '1.14.0', + // }, }, }; }; @@ -58,9 +68,9 @@ export const CreateManagedControlPlaneResource = ( workspaceName: string, ): Resource => { return { - path: `/apis/core.openmcp.cloud/v1alpha1/namespaces/project-${projectName}--ws-${workspaceName}/managedcontrolplanes`, + path: `/apis/core.openmcp.cloud/v1alpha1/namespaces/${projectName}--ws-${workspaceName}/managedcontrolplanes`, method: 'POST', - jq: undefined, + jq: '[.items[] | {metadata: .metadata | {name, namespace}, status: { conditions: [.status.conditions[] | {type: .type, status: .status, message: .message, reason: .reason, lastTransitionTime: .lastTransitionTime}], access: .status.components.authentication.access, status: .status.status } }]', body: undefined, }; }; From 80970fa4897a6c6a604f16f055669d842b0d2dd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Tue, 10 Jun 2025 15:04:08 +0200 Subject: [PATCH 10/31] fixes --- ...eateManagedControlPlaneWizardContainer.tsx | 36 +++++++++---------- .../types/crate/createManagedControlPlane.ts | 12 +------ 2 files changed, 19 insertions(+), 29 deletions(-) diff --git a/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx b/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx index e104e758..aac53155 100644 --- a/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx +++ b/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx @@ -29,7 +29,7 @@ import { CreateManagedControlPlaneResource, CreateManagedControlPlaneType, } from '../../lib/api/types/crate/createManagedControlPlane.ts'; -import { ErrorDialogHandle } from '../Shared/ErrorMessageBox.tsx'; +import { ErrorDialog, ErrorDialogHandle } from '../Shared/ErrorMessageBox.tsx'; import { useToast } from '../../context/ToastContext.tsx'; export type CreateDialogProps = { @@ -71,13 +71,13 @@ export const CreateManagedControlPlaneWizardContainer: FC< const errorDialogRef = useRef(null); const resetFormAndClose = () => { reset(); - setSelectedStep(1); + setSelectedStep('1'); setIsOpen(false); }; console.log(errors); // const { t } = useTranslation(); const { user } = useAuth(); - const [selectedStep, setSelectedStep] = useState(1); + const [selectedStep, setSelectedStep] = useState('1'); const username = user?.email; const toast = useToast(); const clearForm = useCallback(() => { @@ -99,6 +99,7 @@ export const CreateManagedControlPlaneWizardContainer: FC< const { trigger } = useApiResourceMutation( CreateManagedControlPlaneResource(projectName, workspaceName), ); + const handleCreateManagedControlPlane = async ({ name, displayName, @@ -129,15 +130,17 @@ export const CreateManagedControlPlaneWizardContainer: FC< return false; } }; - + const handleStepChange = (e: any) => { + setSelectedStep(e.detail.step.dataset.step); + }; const onNextClick = () => { console.log('test'); console.log(getValues()); - if (selectedStep === 1) { + if (selectedStep === '1') { handleSubmit( () => { console.log('valid'); - setSelectedStep(2); + setSelectedStep('2'); }, () => { console.log('not valid'); @@ -145,7 +148,7 @@ export const CreateManagedControlPlaneWizardContainer: FC< }, )(); } - if (selectedStep === 2) { + if (selectedStep === '2') { handleCreateManagedControlPlane(getValues()); } }; @@ -166,7 +169,7 @@ export const CreateManagedControlPlaneWizardContainer: FC< Close } @@ -174,17 +177,15 @@ export const CreateManagedControlPlaneWizardContainer: FC< } onClose={() => setIsOpen(false)} > - + { - setSelectedStep(1); - }} > + {selectedStep} { - setSelectedStep(2); - }} >

Summarize

@@ -234,6 +232,7 @@ export const CreateManagedControlPlaneWizardContainer: FC<
+ {selectedStep} +
); }; diff --git a/src/lib/api/types/crate/createManagedControlPlane.ts b/src/lib/api/types/crate/createManagedControlPlane.ts index f0d59b13..361f748e 100644 --- a/src/lib/api/types/crate/createManagedControlPlane.ts +++ b/src/lib/api/types/crate/createManagedControlPlane.ts @@ -49,16 +49,6 @@ export const CreateManagedControlPlane = ( }, spec: { members: optional?.members ?? [], - // dataplane: { - // type: 'Gardener', - // gardener: { - // region: 'eu-west-1', - // }, - // }, - // crossplane: { - // enabled: false, - // version: '1.14.0', - // }, }, }; }; @@ -70,7 +60,7 @@ export const CreateManagedControlPlaneResource = ( return { path: `/apis/core.openmcp.cloud/v1alpha1/namespaces/${projectName}--ws-${workspaceName}/managedcontrolplanes`, method: 'POST', - jq: '[.items[] | {metadata: .metadata | {name, namespace}, status: { conditions: [.status.conditions[] | {type: .type, status: .status, message: .message, reason: .reason, lastTransitionTime: .lastTransitionTime}], access: .status.components.authentication.access, status: .status.status } }]', + jq: undefined, body: undefined, }; }; From 64d87858303d771e5138997312cdf09b07b2e82e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Tue, 10 Jun 2025 15:30:22 +0200 Subject: [PATCH 11/31] Update CreateManagedControlPlaneWizardContainer.tsx --- .../CreateManagedControlPlaneWizardContainer.tsx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx b/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx index aac53155..b2dc5744 100644 --- a/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx +++ b/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx @@ -234,7 +234,17 @@ export const CreateManagedControlPlaneWizardContainer: FC<
{selectedStep}
From f94c7726d5a2351e1d4e835fda38a8074f0d46ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Tue, 10 Jun 2025 15:55:07 +0200 Subject: [PATCH 12/31] Update CreateManagedControlPlaneWizardContainer.tsx --- .../Wizards/CreateManagedControlPlaneWizardContainer.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx b/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx index b2dc5744..e20a1005 100644 --- a/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx +++ b/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx @@ -193,6 +193,7 @@ export const CreateManagedControlPlaneWizardContainer: FC< setValue={setValue} /> + Date: Wed, 11 Jun 2025 09:54:13 +0200 Subject: [PATCH 13/31] refactor --- .../Dialogs/CreateProjectWorkspaceDialog.tsx | 97 ++++--------------- src/components/Dialogs/MetadataForm.tsx | 61 ++++++++++++ ...eateManagedControlPlaneWizardContainer.tsx | 51 +++++++--- 3 files changed, 115 insertions(+), 94 deletions(-) create mode 100644 src/components/Dialogs/MetadataForm.tsx diff --git a/src/components/Dialogs/CreateProjectWorkspaceDialog.tsx b/src/components/Dialogs/CreateProjectWorkspaceDialog.tsx index cbf58148..894d8a87 100644 --- a/src/components/Dialogs/CreateProjectWorkspaceDialog.tsx +++ b/src/components/Dialogs/CreateProjectWorkspaceDialog.tsx @@ -1,16 +1,8 @@ -import { - Bar, - Button, - Dialog, - Form, - FormGroup, - Input, - Label, -} from '@ui5/webcomponents-react'; +import { Bar, Button, Dialog, FormGroup } from '@ui5/webcomponents-react'; import { Member } from '../../lib/api/types/shared/members'; import { ErrorDialog, ErrorDialogHandle } from '../Shared/ErrorMessageBox.tsx'; -import styles from './CreateProjectWorkspaceDialog.module.css'; + import { FormEvent, useState } from 'react'; import { KubectlInfoButton } from './KubectlCommandInfo/KubectlInfoButton.tsx'; import { KubectlCreateWorkspaceDialog } from './KubectlCommandInfo/KubectlCreateWorkspaceDialog.tsx'; @@ -22,6 +14,7 @@ import { useTranslation } from 'react-i18next'; import { CreateDialogProps } from './CreateWorkspaceDialogContainer.tsx'; import { FieldErrors, UseFormRegister, UseFormSetValue } from 'react-hook-form'; +import { MetadataForm } from './MetadataForm.tsx'; export type OnCreatePayload = { name: string; @@ -55,12 +48,14 @@ export function CreateProjectWorkspaceDialog({ setValue, projectName, }: CreateProjectWorkspaceDialogProps) { - // const { links } = useFrontendConfig(); const { t } = useTranslation(); const [isKubectlDialogOpen, setIsKubectlDialogOpen] = useState(false); const openKubectlDialog = () => setIsKubectlDialogOpen(true); const closeKubectlDialog = () => setIsKubectlDialogOpen(false); + const setMembers = (members: Member[]) => { + setValue('members', members); + }; return ( <> @@ -89,11 +84,20 @@ export function CreateProjectWorkspaceDialog({ } onClose={() => setIsOpen(false)} > - + + + } />
); } - -interface MetadataAndMembersFormProps { - members: Member[]; - register: UseFormRegister; - errors: FieldErrors; - setValue: UseFormSetValue; -} - -export function MetadataAndMembersForm({ - members, - register, - errors, - setValue, -}: MetadataAndMembersFormProps) { - const { t } = useTranslation(); - - const setMembers = (members: Member[]) => { - setValue('members', members); - }; - return ( - - - - {errors.name?.message}} - required - /> - - - - - - - - - - - - ); -} diff --git a/src/components/Dialogs/MetadataForm.tsx b/src/components/Dialogs/MetadataForm.tsx new file mode 100644 index 00000000..a69f088c --- /dev/null +++ b/src/components/Dialogs/MetadataForm.tsx @@ -0,0 +1,61 @@ +import { FieldErrors, UseFormRegister } from 'react-hook-form'; +import { CreateDialogProps } from './CreateWorkspaceDialogContainer.tsx'; +import { useTranslation } from 'react-i18next'; +import { Form, FormGroup, Input, Label } from '@ui5/webcomponents-react'; +import styles from './CreateProjectWorkspaceDialog.module.css'; + +export interface MetadataFormProps { + register: UseFormRegister; + errors: FieldErrors; + + sideFormContent?: React.ReactNode; +} + +export function MetadataForm({ + register, + errors, + + sideFormContent, +}: MetadataFormProps) { + const { t } = useTranslation(); + + return ( +
+ + + {errors.name?.message}} + required + /> + + + + + + + + {sideFormContent ? sideFormContent : null} +
+ ); +} diff --git a/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx b/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx index e20a1005..fb4d5781 100644 --- a/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx +++ b/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx @@ -3,22 +3,22 @@ import { useApiResourceMutation } from '../../lib/api/useApiResource'; import { useAuth } from '../../spaces/onboarding/auth/AuthContext.tsx'; import { Member, MemberRoles } from '../../lib/api/types/shared/members.ts'; - +import type { WizardStepChangeEventDetail } from '@ui5/webcomponents-fiori/dist/Wizard.js'; import { zodResolver } from '@hookform/resolvers/zod'; import { useForm } from 'react-hook-form'; import { validationSchemaProjectWorkspace } from '../../lib/api/validations/schemas.ts'; -import { - MetadataAndMembersForm, - OnCreatePayload, -} from '../Dialogs/CreateProjectWorkspaceDialog.tsx'; +import { OnCreatePayload } from '../Dialogs/CreateProjectWorkspaceDialog.tsx'; import { Bar, Button, Dialog, + FormGroup, Grid, List, ListItemStandard, + Ui5CustomEvent, Wizard, + WizardDomRef, WizardStep, } from '@ui5/webcomponents-react'; import YamlViewer from '../Yaml/YamlViewer.tsx'; @@ -31,6 +31,9 @@ import { } from '../../lib/api/types/crate/createManagedControlPlane.ts'; import { ErrorDialog, ErrorDialogHandle } from '../Shared/ErrorMessageBox.tsx'; import { useToast } from '../../context/ToastContext.tsx'; +import { EditMembers } from '../Members/EditMembers.tsx'; +import { useTranslation } from 'react-i18next'; +import { MetadataForm } from '../Dialogs/MetadataForm.tsx'; export type CreateDialogProps = { name: string; @@ -130,8 +133,10 @@ export const CreateManagedControlPlaneWizardContainer: FC< return false; } }; - const handleStepChange = (e: any) => { - setSelectedStep(e.detail.step.dataset.step); + const handleStepChange = ( + e: Ui5CustomEvent, + ) => { + setSelectedStep(e.detail.step.dataset.step ?? ''); }; const onNextClick = () => { console.log('test'); @@ -152,6 +157,10 @@ export const CreateManagedControlPlaneWizardContainer: FC< handleCreateManagedControlPlane(getValues()); } }; + const setMembers = (members: Member[]) => { + setValue('members', members); + }; + const { t } = useTranslation(); console.log('selected'); console.log(selectedStep); return ( @@ -185,21 +194,33 @@ export const CreateManagedControlPlaneWizardContainer: FC< selected={selectedStep === '1'} data-step={'1'} > - {selectedStep} - + + + } /> - +

Summarize

From d2e5698f399c8154d46b4ea578fcf4ac45f25e07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Wed, 11 Jun 2025 12:20:43 +0200 Subject: [PATCH 14/31] refactor --- public/locales/en.json | 17 +- src/components/Members/EditMembers.tsx | 42 +++-- src/components/Members/MemberRoleSelect.tsx | 7 +- ...eateManagedControlPlaneWizardContainer.tsx | 148 +++++++++++------- 4 files changed, 137 insertions(+), 77 deletions(-) diff --git a/public/locales/en.json b/public/locales/en.json index 3aa569a0..a178aadf 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -264,15 +264,28 @@ }, "common": { "close": "Close", - "cannotLoadData": "Cannot load data" + "cannotLoadData": "Cannot load data", + "metadata": "Metadata", + "members": "Members", + "summarize": "Summarize", + "namespace": "Namespace", + "region": "Region", + "success": "Success" }, "buttons": { "viewResource": "View resource", "download": "Download", - "copy": "Copy" + "copy": "Copy", + "next": "Next", + "create": "Create", + "close": "Close" }, "yaml": { "copiedToClipboard": "YAML copied to clipboard!", "YAML": "YAML" + }, + "createMCP": { + "titleText": "Success", + "subtitleText": "Your Managed Control plane is being created and will be ready in few minutes" } } diff --git a/src/components/Members/EditMembers.tsx b/src/components/Members/EditMembers.tsx index 38f98006..335e46ee 100644 --- a/src/components/Members/EditMembers.tsx +++ b/src/components/Members/EditMembers.tsx @@ -1,5 +1,11 @@ import { FC, useRef, useState } from 'react'; -import { Button, Input, InputDomRef } from '@ui5/webcomponents-react'; +import { + Button, + FlexBox, + Input, + InputDomRef, + Label, +} from '@ui5/webcomponents-react'; import { MemberTable } from './MemberTable.tsx'; import { MemberRoleSelect } from './MemberRoleSelect.tsx'; import { ValueState } from '../Shared/Ui5ValieState.tsx'; @@ -55,21 +61,25 @@ export const EditMembers: FC = ({ return ( <>
- {valueStateMessage}} - onChange={() => { - setHighlightEmail('None'); - }} - /> - - + + + + {valueStateMessage}} + onChange={() => { + setHighlightEmail('None'); + }} + /> + + + + + + - + ); } diff --git a/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx b/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx index fb4d5781..aaa8e48d 100644 --- a/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx +++ b/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx @@ -1,6 +1,6 @@ -import { FC, useCallback, useEffect, useRef, useState } from 'react'; +import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useApiResourceMutation } from '../../lib/api/useApiResource'; - +import IllustrationMessageType from '@ui5/webcomponents-fiori/dist/types/IllustrationMessageType.js'; import { useAuth } from '../../spaces/onboarding/auth/AuthContext.tsx'; import { Member, MemberRoles } from '../../lib/api/types/shared/members.ts'; import type { WizardStepChangeEventDetail } from '@ui5/webcomponents-fiori/dist/Wizard.js'; @@ -12,6 +12,7 @@ import { Bar, Button, Dialog, + Form, FormGroup, Grid, List, @@ -30,10 +31,11 @@ import { CreateManagedControlPlaneType, } from '../../lib/api/types/crate/createManagedControlPlane.ts'; import { ErrorDialog, ErrorDialogHandle } from '../Shared/ErrorMessageBox.tsx'; -import { useToast } from '../../context/ToastContext.tsx'; + import { EditMembers } from '../Members/EditMembers.tsx'; import { useTranslation } from 'react-i18next'; import { MetadataForm } from '../Dialogs/MetadataForm.tsx'; +import { IllustratedBanner } from '../Ui/IllustratedBanner/IllustratedBanner.tsx'; export type CreateDialogProps = { name: string; @@ -49,6 +51,8 @@ type CreateManagedControlPlaneWizardContainerProps = { workspaceName?: string; }; +type WizardStep = 'metadata' | 'members' | 'summarize' | 'success'; + export const CreateManagedControlPlaneWizardContainer: FC< CreateManagedControlPlaneWizardContainerProps > = ({ isOpen, setIsOpen, projectName = '', workspaceName = '' }) => { @@ -71,18 +75,28 @@ export const CreateManagedControlPlaneWizardContainer: FC< members: [], }, }); + const { t } = useTranslation(); + const nextBtText = useMemo( + () => ({ + metadata: t('buttons.next'), + members: t('buttons.next'), + summarize: t('buttons.create'), + success: t('buttons.close'), + }), + [], + ); const errorDialogRef = useRef(null); const resetFormAndClose = () => { reset(); - setSelectedStep('1'); + setSelectedStep('metadata'); setIsOpen(false); }; console.log(errors); // const { t } = useTranslation(); const { user } = useAuth(); - const [selectedStep, setSelectedStep] = useState('1'); + const [selectedStep, setSelectedStep] = useState('metadata'); const username = user?.email; - const toast = useToast(); + const clearForm = useCallback(() => { resetField('name'); resetField('chargingTarget'); @@ -117,9 +131,8 @@ export const CreateManagedControlPlaneWizardContainer: FC< members: members, }), ); - // await revalidate(); - setIsOpen(false); - toast.show('mcp created'); + + setSelectedStep('success'); return true; } catch (e) { console.error(e); @@ -136,33 +149,33 @@ export const CreateManagedControlPlaneWizardContainer: FC< const handleStepChange = ( e: Ui5CustomEvent, ) => { - setSelectedStep(e.detail.step.dataset.step ?? ''); + setSelectedStep((e.detail.step.dataset.step ?? '') as WizardStep); }; const onNextClick = () => { - console.log('test'); - console.log(getValues()); - if (selectedStep === '1') { + if (selectedStep === 'metadata') { handleSubmit( () => { - console.log('valid'); - setSelectedStep('2'); + setSelectedStep('members'); }, () => { - console.log('not valid'); console.log(errors); }, )(); } - if (selectedStep === '2') { + if (selectedStep === 'members') { + setSelectedStep('summarize'); + } + if (selectedStep === 'summarize') { handleCreateManagedControlPlane(getValues()); } + if (selectedStep === 'success') { + setIsOpen(false); + } }; const setMembers = (members: Member[]) => { setValue('members', members); }; - const { t } = useTranslation(); - console.log('selected'); - console.log(selectedStep); + return (
} @@ -189,61 +202,70 @@ export const CreateManagedControlPlaneWizardContainer: FC< - - - - } - /> + + titleText={t('common.members')} + selected={selectedStep === 'members'} + data-step={'members'} + disabled={selectedStep === 'metadata' || !isValid} + > +
+ + + +
+
-

Summarize

+

{t('common.summarize')}

- + {' '} - {' '} - + /> + +
- + {getValues('members').map((member) => (
- {selectedStep}
+ + +
From 44476953eaff08843cfa42cd609da0893fcae52b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Wed, 11 Jun 2025 12:41:30 +0200 Subject: [PATCH 15/31] Update CreateManagedControlPlaneWizardContainer.tsx --- .../CreateManagedControlPlaneWizardContainer.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx b/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx index aaa8e48d..30a4e511 100644 --- a/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx +++ b/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx @@ -170,6 +170,8 @@ export const CreateManagedControlPlaneWizardContainer: FC< } if (selectedStep === 'success') { setIsOpen(false); + clearForm(); + setSelectedStep('metadata'); } }; const setMembers = (members: Member[]) => { @@ -187,9 +189,11 @@ export const CreateManagedControlPlaneWizardContainer: FC< design="Footer" endContent={
- + {selectedStep !== 'success' && ( + + )} From ae5bd0e10d71bc926a46acb6db5f2801182625e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Wed, 11 Jun 2025 12:50:53 +0200 Subject: [PATCH 16/31] Update CreateManagedControlPlaneWizardContainer.tsx --- ...eateManagedControlPlaneWizardContainer.tsx | 262 +++++++++++------- 1 file changed, 162 insertions(+), 100 deletions(-) diff --git a/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx b/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx index 30a4e511..ffd15897 100644 --- a/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx +++ b/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx @@ -51,11 +51,17 @@ type CreateManagedControlPlaneWizardContainerProps = { workspaceName?: string; }; -type WizardStep = 'metadata' | 'members' | 'summarize' | 'success'; +type WizardStepType = 'metadata' | 'members' | 'summarize' | 'success'; export const CreateManagedControlPlaneWizardContainer: FC< CreateManagedControlPlaneWizardContainerProps > = ({ isOpen, setIsOpen, projectName = '', workspaceName = '' }) => { + const { t } = useTranslation(); + const { user } = useAuth(); + const errorDialogRef = useRef(null); + + const [selectedStep, setSelectedStep] = useState('metadata'); + const { register, handleSubmit, @@ -63,7 +69,6 @@ export const CreateManagedControlPlaneWizardContainer: FC< setValue, reset, getValues, - formState: { errors, isValid }, watch, } = useForm({ @@ -74,150 +79,211 @@ export const CreateManagedControlPlaneWizardContainer: FC< chargingTarget: '', members: [], }, + mode: 'onChange', }); - const { t } = useTranslation(); - const nextBtText = useMemo( + + // Memoize next button text to avoid unnecessary recalculations + const nextButtonText = useMemo( () => ({ metadata: t('buttons.next'), members: t('buttons.next'), summarize: t('buttons.create'), success: t('buttons.close'), }), - [], + [t], ); - const errorDialogRef = useRef(null); - const resetFormAndClose = () => { + + // Helper to reset form and close dialog + const resetFormAndClose = useCallback(() => { reset(); setSelectedStep('metadata'); setIsOpen(false); - }; - console.log(errors); - // const { t } = useTranslation(); - const { user } = useAuth(); - const [selectedStep, setSelectedStep] = useState('metadata'); - const username = user?.email; + }, [reset, setIsOpen]); - const clearForm = useCallback(() => { + // Helper to clear only the form fields, not the members + const clearFormFields = useCallback(() => { resetField('name'); resetField('chargingTarget'); resetField('displayName'); }, [resetField]); + // Set default member (current user) on open, clear fields on close useEffect(() => { - if (username) { + if (user?.email && isOpen) { setValue('members', [ - { name: username, roles: [MemberRoles.admin], kind: 'User' }, + { name: user.email, roles: [MemberRoles.admin], kind: 'User' }, ]); } if (!isOpen) { - clearForm(); + clearFormFields(); } - }, [resetField, setValue, username, isOpen, clearForm]); + // Only run when dialog open/close or user changes + }, [user?.email, isOpen, setValue, clearFormFields]); + + // API mutation hook const { trigger } = useApiResourceMutation( CreateManagedControlPlaneResource(projectName, workspaceName), ); - const handleCreateManagedControlPlane = async ({ - name, - displayName, - chargingTarget, - members, - }: OnCreatePayload): Promise => { - try { - await trigger( - CreateManagedControlPlane(name, `${projectName}--ws-${workspaceName}`, { - displayName: displayName, - chargingTarget: chargingTarget, - members: members, - }), - ); - - setSelectedStep('success'); - return true; - } catch (e) { - console.error(e); - if (e instanceof APIError) { - if (errorDialogRef.current) { + // Handles the creation API call and error dialog + const handleCreateManagedControlPlane = useCallback( + async ({ + name, + displayName, + chargingTarget, + members, + }: OnCreatePayload): Promise => { + try { + await trigger( + CreateManagedControlPlane( + name, + `${projectName}--ws-${workspaceName}`, + { + displayName, + chargingTarget, + members, + }, + ), + ); + setSelectedStep('success'); + return true; + } catch (e) { + // Only show error dialog for APIError + if (e instanceof APIError && errorDialogRef.current) { errorDialogRef.current.showErrorDialog( `${e.message}: ${JSON.stringify(e.info)}`, ); + } else { + console.error(e); } + return false; } - return false; - } - }; - const handleStepChange = ( - e: Ui5CustomEvent, - ) => { - setSelectedStep((e.detail.step.dataset.step ?? '') as WizardStep); - }; - const onNextClick = () => { - if (selectedStep === 'metadata') { - handleSubmit( - () => { - setSelectedStep('members'); - }, - () => { - console.log(errors); - }, - )(); - } - if (selectedStep === 'members') { - setSelectedStep('summarize'); - } - if (selectedStep === 'summarize') { - handleCreateManagedControlPlane(getValues()); - } - if (selectedStep === 'success') { - setIsOpen(false); - clearForm(); - setSelectedStep('metadata'); + }, + [trigger, projectName, workspaceName], + ); + + // Handles wizard step change (if user clicks on step header) + const handleStepChange = useCallback( + (e: Ui5CustomEvent) => { + const step = (e.detail.step.dataset.step ?? '') as WizardStepType; + setSelectedStep(step); + }, + [], + ); + + // Handles the Next/Create/Close button logic + const onNextClick = useCallback(() => { + switch (selectedStep) { + case 'metadata': + handleSubmit( + () => setSelectedStep('members'), + () => { + // Optionally, show a toast or error message here + }, + )(); + break; + case 'members': + setSelectedStep('summarize'); + break; + case 'summarize': + handleCreateManagedControlPlane(getValues()); + break; + case 'success': + resetFormAndClose(); + break; + default: + break; } - }; - const setMembers = (members: Member[]) => { - setValue('members', members); - }; + }, [ + selectedStep, + handleSubmit, + setSelectedStep, + handleCreateManagedControlPlane, + getValues, + resetFormAndClose, + ]); + + // Update members in form state + const setMembers = useCallback( + (members: Member[]) => { + setValue('members', members, { shouldValidate: true }); + }, + [setValue], + ); + + // Helper to determine if a step should be disabled + const isStepDisabled = useCallback( + (step: WizardStepType) => { + switch (step) { + case 'metadata': + return false; + case 'members': + return selectedStep === 'metadata' || !isValid; + case 'summarize': + return ( + selectedStep === 'metadata' || + selectedStep === 'members' || + !isValid + ); + case 'success': + // Success step should only be enabled when selectedStep is 'success' + return selectedStep !== 'success'; + default: + return false; + } + }, + [selectedStep, isValid], + ); + // Render return ( +
{selectedStep !== 'success' && ( - )} -
} /> } - onClose={() => setIsOpen(false)} + data-testid="create-mcp-dialog" + onClose={resetFormAndClose} > - +

{t('common.summarize')}

- + + {/* If region is available, show it; otherwise, leave blank */} Date: Wed, 11 Jun 2025 13:17:50 +0200 Subject: [PATCH 17/31] Update CreateManagedControlPlaneWizardContainer.tsx --- ...eateManagedControlPlaneWizardContainer.tsx | 27 +++---------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx b/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx index ffd15897..3c44bb6f 100644 --- a/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx +++ b/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx @@ -82,7 +82,6 @@ export const CreateManagedControlPlaneWizardContainer: FC< mode: 'onChange', }); - // Memoize next button text to avoid unnecessary recalculations const nextButtonText = useMemo( () => ({ metadata: t('buttons.next'), @@ -93,21 +92,18 @@ export const CreateManagedControlPlaneWizardContainer: FC< [t], ); - // Helper to reset form and close dialog const resetFormAndClose = useCallback(() => { reset(); setSelectedStep('metadata'); setIsOpen(false); }, [reset, setIsOpen]); - // Helper to clear only the form fields, not the members const clearFormFields = useCallback(() => { resetField('name'); resetField('chargingTarget'); resetField('displayName'); }, [resetField]); - // Set default member (current user) on open, clear fields on close useEffect(() => { if (user?.email && isOpen) { setValue('members', [ @@ -117,15 +113,12 @@ export const CreateManagedControlPlaneWizardContainer: FC< if (!isOpen) { clearFormFields(); } - // Only run when dialog open/close or user changes }, [user?.email, isOpen, setValue, clearFormFields]); - // API mutation hook const { trigger } = useApiResourceMutation( CreateManagedControlPlaneResource(projectName, workspaceName), ); - // Handles the creation API call and error dialog const handleCreateManagedControlPlane = useCallback( async ({ name, @@ -148,7 +141,6 @@ export const CreateManagedControlPlaneWizardContainer: FC< setSelectedStep('success'); return true; } catch (e) { - // Only show error dialog for APIError if (e instanceof APIError && errorDialogRef.current) { errorDialogRef.current.showErrorDialog( `${e.message}: ${JSON.stringify(e.info)}`, @@ -162,7 +154,6 @@ export const CreateManagedControlPlaneWizardContainer: FC< [trigger, projectName, workspaceName], ); - // Handles wizard step change (if user clicks on step header) const handleStepChange = useCallback( (e: Ui5CustomEvent) => { const step = (e.detail.step.dataset.step ?? '') as WizardStepType; @@ -171,16 +162,10 @@ export const CreateManagedControlPlaneWizardContainer: FC< [], ); - // Handles the Next/Create/Close button logic const onNextClick = useCallback(() => { switch (selectedStep) { case 'metadata': - handleSubmit( - () => setSelectedStep('members'), - () => { - // Optionally, show a toast or error message here - }, - )(); + handleSubmit(() => setSelectedStep('members'))(); break; case 'members': setSelectedStep('summarize'); @@ -203,7 +188,6 @@ export const CreateManagedControlPlaneWizardContainer: FC< resetFormAndClose, ]); - // Update members in form state const setMembers = useCallback( (members: Member[]) => { setValue('members', members, { shouldValidate: true }); @@ -211,7 +195,6 @@ export const CreateManagedControlPlaneWizardContainer: FC< [setValue], ); - // Helper to determine if a step should be disabled const isStepDisabled = useCallback( (step: WizardStepType) => { switch (step) { @@ -226,7 +209,6 @@ export const CreateManagedControlPlaneWizardContainer: FC< !isValid ); case 'success': - // Success step should only be enabled when selectedStep is 'success' return selectedStep !== 'success'; default: return false; @@ -235,7 +217,6 @@ export const CreateManagedControlPlaneWizardContainer: FC< [selectedStep, isValid], ); - // Render return ( + - {/* If region is available, show it; otherwise, leave blank */} +
@@ -373,7 +355,6 @@ export const CreateManagedControlPlaneWizardContainer: FC< /> -
); }; From e3a2f4eb1e2e8f5f407fa6c6f791e2bd83796d2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Wed, 11 Jun 2025 13:26:49 +0200 Subject: [PATCH 18/31] fixes --- public/locales/en.json | 8 +- src/components/Members/EditMembers.tsx | 120 +++++++++++++------------ src/components/Members/MemberTable.tsx | 44 +++++---- 3 files changed, 95 insertions(+), 77 deletions(-) diff --git a/public/locales/en.json b/public/locales/en.json index a178aadf..357b6dc7 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -270,7 +270,9 @@ "summarize": "Summarize", "namespace": "Namespace", "region": "Region", - "success": "Success" + "success": "Success", + "displayName": "Display Name", + "name": "Name" }, "buttons": { "viewResource": "View resource", @@ -285,7 +287,7 @@ "YAML": "YAML" }, "createMCP": { - "titleText": "Success", - "subtitleText": "Your Managed Control plane is being created and will be ready in few minutes" + "titleText": "Managed Control Plane Created Successfully!", + "subtitleText": "Your Managed Control Plane is being set up. It will be ready to use in just a few minutes. You can safely close this window." } } diff --git a/src/components/Members/EditMembers.tsx b/src/components/Members/EditMembers.tsx index 335e46ee..176f745f 100644 --- a/src/components/Members/EditMembers.tsx +++ b/src/components/Members/EditMembers.tsx @@ -1,4 +1,4 @@ -import { FC, useRef, useState } from 'react'; +import { FC, useRef, useState, useCallback } from 'react'; import { Button, FlexBox, @@ -19,73 +19,83 @@ export interface EditMembersProps { } export const EditMembers: FC = ({ - members = [], + members, onMemberChanged, isValidationError = false, }) => { - const emailInput = useRef(null); - const [valueStateMessage, setValueStateMessage] = useState(''); - const [highlightEmail, setHighlightEmail] = useState('None'); - const [role, setRole] = useState(MemberRoles.viewer); + const emailInputRef = useRef(null); + const [emailState, setEmailState] = useState('None'); + const [emailMessage, setEmailMessage] = useState(''); + const [selectedRole, setSelectedRole] = useState(MemberRoles.viewer); const { t } = useTranslation(); - const addMember = () => { - setValueStateMessage(''); - setHighlightEmail('None'); - if (!emailInput.current) { + const handleAddMember = useCallback(() => { + setEmailState('None'); + setEmailMessage(''); + const input = emailInputRef.current; + const email = input?.value.trim() || ''; + if (!email) { + setEmailState('Negative'); + setEmailMessage(t('validationErrors.required')); return; } - // Check if the email is already in the list, highlight as error - if (members.find((m) => m.name === emailInput.current!.value)) { - setValueStateMessage(t('validationErrors.userExists')); - setHighlightEmail('Negative'); + if (members.some((m) => m.name === email)) { + setEmailState('Negative'); + setEmailMessage(t('validationErrors.userExists')); return; } - - const newMembers = [ + onMemberChanged([ ...members, - { name: emailInput.current.value, roles: [role], kind: 'User' }, - ]; - onMemberChanged(newMembers); - emailInput.current!.value = ''; - }; - const removeMember = (email: string) => { - const newMembers = members.filter((m) => m.name !== email); - onMemberChanged(newMembers); - }; + { name: email, roles: [selectedRole], kind: 'User' }, + ]); + if (input) input.value = ''; + }, [members, onMemberChanged, selectedRole, t]); + + const handleRemoveMember = useCallback( + (email: string) => { + onMemberChanged(members.filter((m) => m.name !== email)); + }, + [members, onMemberChanged], + ); + + const handleRoleChange = useCallback((role: MemberRoles) => { + setSelectedRole(role); + }, []); - const changeSelectedRole = (role: MemberRoles) => { - setRole(role); - }; + const handleEmailInputChange = useCallback(() => { + setEmailState('None'); + setEmailMessage(''); + }, []); return ( - <> -
- - - - {valueStateMessage}} - onChange={() => { - setHighlightEmail('None'); - }} - /> - - - + + + + + {emailMessage}} + data-testid="member-email-input" + onInput={handleEmailInputChange} + /> - -
- + + + + + ); }; diff --git a/src/components/Members/MemberTable.tsx b/src/components/Members/MemberTable.tsx index 1600ac3a..91f28928 100644 --- a/src/components/Members/MemberTable.tsx +++ b/src/components/Members/MemberTable.tsx @@ -8,12 +8,25 @@ import { useTranslation } from 'react-i18next'; import { FC } from 'react'; import { Infobox } from '../Ui/Infobox/Infobox.tsx'; +type MemberTableRow = { + email: string; + role: string; +}; + type MemberTableProps = { members: Member[]; onDeleteMember?: (email: string) => void; isValidationError?: boolean; }; +type CellInstance = { + cell: { + row: { + original: MemberTableRow; + }; + }; +}; + export const MemberTable: FC = ({ members, onDeleteMember, @@ -37,43 +50,36 @@ export const MemberTable: FC = ({ Header: '', accessor: '.', width: 50, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - Cell: (instance: any) => ( + Cell: (instance: CellInstance) => ( - )} - + ) : ( + + ))} +
From d3a30f60b45af02771556024ebc1c52a610e79f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Thu, 12 Jun 2025 13:50:11 +0200 Subject: [PATCH 22/31] refactor --- src/components/Members/EditMembers.tsx | 3 +++ src/components/Members/MemberTable.tsx | 4 +++- .../Wizards/CreateManagedControlPlaneWizardContainer.tsx | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/Members/EditMembers.tsx b/src/components/Members/EditMembers.tsx index 176f745f..e12045f9 100644 --- a/src/components/Members/EditMembers.tsx +++ b/src/components/Members/EditMembers.tsx @@ -16,12 +16,14 @@ export interface EditMembersProps { members: Member[]; onMemberChanged: (members: Member[]) => void; isValidationError?: boolean; + requireAtLeastOneMember?: boolean; } export const EditMembers: FC = ({ members, onMemberChanged, isValidationError = false, + requireAtLeastOneMember = true, }) => { const emailInputRef = useRef(null); const [emailState, setEmailState] = useState('None'); @@ -92,6 +94,7 @@ export const EditMembers: FC = ({
void; isValidationError?: boolean; + requireAtLeastOneMember: boolean; }; type CellInstance = { @@ -31,6 +32,7 @@ export const MemberTable: FC = ({ members, onDeleteMember, isValidationError = false, + requireAtLeastOneMember, }) => { const { t } = useTranslation(); @@ -62,7 +64,7 @@ export const MemberTable: FC = ({ }); } - if (members.length === 0) { + if (requireAtLeastOneMember && members.length === 0) { return ( From f3561d1c7f256da629a7318c07979de39f747a29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Thu, 12 Jun 2025 14:45:07 +0200 Subject: [PATCH 23/31] fixes --- public/locales/en.json | 4 ++-- .../ControlPlanes/List/ControlPlaneListWorkspaceGridTile.tsx | 3 ++- src/components/ControlPlanes/List/WorkspacesList.module.css | 4 ++++ src/components/Members/EditMembers.tsx | 3 ++- src/components/Members/Members.module.css | 3 +++ src/index.css | 3 ++- 6 files changed, 15 insertions(+), 5 deletions(-) create mode 100644 src/components/ControlPlanes/List/WorkspacesList.module.css create mode 100644 src/components/Members/Members.module.css diff --git a/public/locales/en.json b/public/locales/en.json index 7ab62218..5c8a978f 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -81,8 +81,8 @@ "menuCopy": "Copy to clipboard" }, "IllustratedBanner": { - "titleMessage": "You donโ€™t have any Managed Control Planes", - "subtitleMessage": "Create a new Managed Control Plane to get started", + "titleMessage": "No Managed Control Planes found", + "subtitleMessage": "Get started by creating your first Managed Control Plane.", "helpButton": "Help" }, "IntelligentBreadcrumbs": { diff --git a/src/components/ControlPlanes/List/ControlPlaneListWorkspaceGridTile.tsx b/src/components/ControlPlanes/List/ControlPlaneListWorkspaceGridTile.tsx index 71340410..47b9a283 100644 --- a/src/components/ControlPlanes/List/ControlPlaneListWorkspaceGridTile.tsx +++ b/src/components/ControlPlanes/List/ControlPlaneListWorkspaceGridTile.tsx @@ -37,7 +37,7 @@ import { YamlViewButtonWithLoader } from '../../Yaml/YamlViewButtonWithLoader.ts import { IllustratedBanner } from '../../Ui/IllustratedBanner/IllustratedBanner.tsx'; import { useLink } from '../../../lib/shared/useLink.ts'; import IllustrationMessageType from '@ui5/webcomponents-fiori/dist/types/IllustrationMessageType.js'; - +import styles from './WorkspacesList.module.css'; import { ControlPlanesListMenu } from '../ControlPlanesListMenu.tsx'; import { CreateManagedControlPlaneWizardContainer } from '../../Wizards/CreateManagedControlPlaneWizardContainer.tsx'; @@ -166,6 +166,7 @@ export function ControlPlaneListWorkspaceGridTile({ }} button={ ) : ( - + ))} +