Skip to content

Commit 290cd3a

Browse files
committed
Wizard: Set up custom footers
This sets up custom footers for the new Wizard. Both the main and reviews footers are now located in components subfolder next to the Wizard. The review footer for the old wizard is placed in the Review step, which feels less convenient.
1 parent 06346f8 commit 290cd3a

File tree

3 files changed

+287
-4
lines changed

3 files changed

+287
-4
lines changed

src/Components/CreateImageWizard3/CreateImageWizard3.tsx

Lines changed: 77 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,17 @@ import {
1515

1616
import { useGetBlueprintQuery } from '@/store/api/backend';
1717
import { selectSelectedBlueprintId } from '@/store/slices/blueprint';
18+
import { selectIsOnPremise } from '@/store/slices/env';
1819
import { loadWizardState } from '@/store/slices/wizard';
1920
import {
2021
closeWizardModal,
2122
selectIsWizardModalOpen,
2223
selectWizardModalMode,
2324
} from '@/store/slices/wizardModal';
2425

26+
import CustomWizardFooter from './components/CustomWizardFooter';
27+
import ReviewWizardFooter from './components/ReviewWizardFooter';
28+
2529
import { useAppDispatch, useAppSelector } from '../../store/hooks';
2630
import DetailsStep from '../CreateImageWizard/steps/Details';
2731
import FileSystemStep from '../CreateImageWizard/steps/FileSystem';
@@ -41,18 +45,45 @@ import RepeatableBuildStep from '../CreateImageWizard/steps/Snapshot';
4145
import TimezoneStep from '../CreateImageWizard/steps/Timezone';
4246
import UsersStep from '../CreateImageWizard/steps/UsersAndGroups';
4347
import { mapRequestToState } from '../CreateImageWizard/utilities/requestMapper';
48+
import {
49+
useDetailsValidation,
50+
useFilesystemValidation,
51+
useFirewallValidation,
52+
useFirstBootValidation,
53+
useHostnameValidation,
54+
useKernelValidation,
55+
useLocaleValidation,
56+
useRegistrationValidation,
57+
useServicesValidation,
58+
useSnapshotValidation,
59+
useTimezoneValidation,
60+
} from '../CreateImageWizard/utilities/useValidation';
4461

4562
export const CreateImageWizard3 = () => {
4663
const dispatch = useAppDispatch();
4764
const showWizardModal = useAppSelector(selectIsWizardModalOpen);
4865
const mode = useAppSelector(selectWizardModalMode);
4966
const blueprintId = useAppSelector(selectSelectedBlueprintId);
67+
const isOnPremise = useAppSelector(selectIsOnPremise);
5068

5169
const { data: blueprintDetails, isSuccess } = useGetBlueprintQuery(
5270
{ id: blueprintId || '' },
5371
{ skip: !(mode === 'edit' && !!blueprintId) },
5472
);
5573

74+
// Validation hooks
75+
const detailsValidation = useDetailsValidation();
76+
const registrationValidation = useRegistrationValidation();
77+
const snapshotValidation = useSnapshotValidation();
78+
const filesystemValidation = useFilesystemValidation();
79+
const timezoneValidation = useTimezoneValidation();
80+
const localeValidation = useLocaleValidation();
81+
const hostnameValidation = useHostnameValidation();
82+
const kernelValidation = useKernelValidation();
83+
const servicesValidation = useServicesValidation();
84+
const firewallValidation = useFirewallValidation();
85+
const firstBootValidation = useFirstBootValidation();
86+
5687
useEffect(() => {
5788
if (mode === 'edit' && blueprintId && blueprintDetails) {
5889
const editBlueprintState = mapRequestToState(blueprintDetails);
@@ -102,7 +133,21 @@ export const CreateImageWizard3 = () => {
102133
/>
103134
}
104135
>
105-
<WizardStep name='Base settings' id='base-settings-step'>
136+
<WizardStep
137+
name='Base settings'
138+
id='base-settings-step'
139+
footer={
140+
<CustomWizardFooter
141+
disableBack={true}
142+
disableNext={
143+
detailsValidation.disabledNext ||
144+
registrationValidation.disabledNext ||
145+
snapshotValidation.disabledNext
146+
}
147+
isOnPremise={isOnPremise}
148+
/>
149+
}
150+
>
106151
<Form>
107152
<Title headingLevel='h1' size='xl'>
108153
Basic image settings
@@ -122,7 +167,13 @@ export const CreateImageWizard3 = () => {
122167
<OscapStep />
123168
</Form>
124169
</WizardStep>
125-
<WizardStep name='Repositories and packages' id='content-step'>
170+
<WizardStep
171+
name='Repositories and packages'
172+
id='content-step'
173+
footer={
174+
<CustomWizardFooter disableNext={false} isOnPremise={isOnPremise} />
175+
}
176+
>
126177
<Form>
127178
<Title headingLevel='h1' size='xl'>
128179
Repositories and packages
@@ -138,7 +189,25 @@ export const CreateImageWizard3 = () => {
138189
<PackagesStep />
139190
</Form>
140191
</WizardStep>
141-
<WizardStep name='Advanced settings' id='advance-settings-step'>
192+
<WizardStep
193+
name='Advanced settings'
194+
id='advance-settings-step'
195+
footer={
196+
<CustomWizardFooter
197+
disableNext={
198+
filesystemValidation.disabledNext ||
199+
timezoneValidation.disabledNext ||
200+
localeValidation.disabledNext ||
201+
hostnameValidation.disabledNext ||
202+
kernelValidation.disabledNext ||
203+
servicesValidation.disabledNext ||
204+
firewallValidation.disabledNext ||
205+
firstBootValidation.disabledNext
206+
}
207+
isOnPremise={isOnPremise}
208+
/>
209+
}
210+
>
142211
<Form>
143212
<Title headingLevel='h1' size='xl'>
144213
Advanced settings
@@ -168,7 +237,11 @@ export const CreateImageWizard3 = () => {
168237
<FirstBootStep />
169238
</Form>
170239
</WizardStep>
171-
<WizardStep name='Review' id='review-step'>
240+
<WizardStep
241+
name='Review'
242+
id='review-step'
243+
footer={<ReviewWizardFooter />}
244+
>
172245
<Form>
173246
<Title headingLevel='h1' size='xl'>
174247
Review image configuration
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import React from 'react';
2+
3+
import {
4+
Button,
5+
Flex,
6+
useWizardContext,
7+
WizardFooterWrapper,
8+
} from '@patternfly/react-core';
9+
import useChrome from '@redhat-cloud-services/frontend-components/useChrome';
10+
11+
import { AMPLITUDE_MODULE_NAME } from '@/constants';
12+
13+
type CustomWizardFooterPropType = {
14+
disableBack?: boolean;
15+
disableNext: boolean;
16+
beforeNext?: () => boolean;
17+
isOnPremise: boolean;
18+
};
19+
20+
export const CustomWizardFooter = ({
21+
disableBack,
22+
disableNext,
23+
beforeNext,
24+
isOnPremise,
25+
}: CustomWizardFooterPropType) => {
26+
const { goToNextStep, goToPrevStep, close, activeStep } = useWizardContext();
27+
const { analytics } = useChrome();
28+
const cancelBtnID = 'wizard-cancel-btn';
29+
30+
return (
31+
<WizardFooterWrapper>
32+
<Flex columnGap={{ default: 'columnGapSm' }}>
33+
<Button
34+
variant='primary'
35+
onClick={() => {
36+
if (!beforeNext || beforeNext()) goToNextStep();
37+
}}
38+
isDisabled={disableNext}
39+
>
40+
Next
41+
</Button>
42+
<Button
43+
variant='secondary'
44+
onClick={() => {
45+
goToPrevStep();
46+
}}
47+
isDisabled={disableBack || false}
48+
>
49+
Back
50+
</Button>
51+
<Button
52+
variant='link'
53+
onClick={() => {
54+
if (!isOnPremise) {
55+
analytics.track(`${AMPLITUDE_MODULE_NAME} - Button Clicked`, {
56+
module: AMPLITUDE_MODULE_NAME,
57+
button_id: cancelBtnID,
58+
active_step_id: activeStep.id,
59+
});
60+
}
61+
close();
62+
}}
63+
>
64+
Cancel
65+
</Button>
66+
</Flex>
67+
</WizardFooterWrapper>
68+
);
69+
};
70+
71+
export default CustomWizardFooter;
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import React, { useEffect, useState } from 'react';
2+
3+
import {
4+
Button,
5+
Dropdown,
6+
Flex,
7+
MenuToggle,
8+
useWizardContext,
9+
WizardFooterWrapper,
10+
} from '@patternfly/react-core';
11+
import { MenuToggleElement } from '@patternfly/react-core/dist/esm/components/MenuToggle/MenuToggle';
12+
import useChrome from '@redhat-cloud-services/frontend-components/useChrome';
13+
import { useStore } from 'react-redux';
14+
15+
import { selectSelectedBlueprintId } from '@/store/slices/blueprint';
16+
import { selectIsOnPremise } from '@/store/slices/env';
17+
import { selectOrgId } from '@/store/slices/wizard';
18+
import { selectWizardModalMode } from '@/store/slices/wizardModal';
19+
20+
import {
21+
useCreateBPWithNotification as useCreateBlueprintMutation,
22+
useUpdateBPWithNotification as useUpdateBlueprintMutation,
23+
} from '../../../Hooks';
24+
import { useAppSelector } from '../../../store/hooks';
25+
import {
26+
CreateSaveAndBuildBtn,
27+
CreateSaveButton,
28+
} from '../../CreateImageWizard/steps/Review/Footer/CreateDropdown';
29+
import {
30+
EditSaveAndBuildBtn,
31+
EditSaveButton,
32+
} from '../../CreateImageWizard/steps/Review/Footer/EditDropdown';
33+
import { mapRequestFromState } from '../../CreateImageWizard/utilities/requestMapper';
34+
import { useIsBlueprintValid } from '../../CreateImageWizard/utilities/useValidation';
35+
36+
const ReviewWizardFooter = () => {
37+
const { goToPrevStep, close } = useWizardContext();
38+
const { isSuccess: isCreateSuccess, reset: resetCreate } =
39+
useCreateBlueprintMutation({ fixedCacheKey: 'createBlueprintKey' });
40+
41+
const { isSuccess: isUpdateSuccess, reset: resetUpdate } =
42+
useUpdateBlueprintMutation({ fixedCacheKey: 'updateBlueprintKey' });
43+
const { auth } = useChrome();
44+
const isOnPremise = useAppSelector(selectIsOnPremise);
45+
const mode = useAppSelector(selectWizardModalMode);
46+
const blueprintId = useAppSelector(selectSelectedBlueprintId);
47+
const [isOpen, setIsOpen] = useState(false);
48+
const store = useStore();
49+
const onToggleClick = () => {
50+
setIsOpen(!isOpen);
51+
};
52+
const isValid = useIsBlueprintValid();
53+
const orgId = useAppSelector(selectOrgId);
54+
55+
useEffect(() => {
56+
if (isUpdateSuccess || isCreateSuccess) {
57+
resetCreate();
58+
resetUpdate();
59+
close();
60+
}
61+
}, [isUpdateSuccess, isCreateSuccess, resetCreate, resetUpdate, close]);
62+
63+
const getBlueprintPayload = async () => {
64+
if (!isOnPremise) {
65+
const userData = await auth.getUser();
66+
const orgId = userData?.identity.internal?.org_id;
67+
const requestBody = orgId && mapRequestFromState(store, orgId);
68+
return requestBody;
69+
}
70+
71+
return mapRequestFromState(store, orgId ?? '');
72+
};
73+
74+
const isEditMode = mode === 'edit';
75+
76+
return (
77+
<WizardFooterWrapper>
78+
<Flex columnGap={{ default: 'columnGapSm' }}>
79+
<Dropdown
80+
isOpen={isOpen}
81+
onOpenChange={(isOpen: boolean) => setIsOpen(isOpen)}
82+
toggle={(toggleRef: React.Ref<MenuToggleElement>) => (
83+
<MenuToggle
84+
variant='primary'
85+
ref={toggleRef}
86+
onClick={onToggleClick}
87+
isExpanded={isOpen}
88+
isDisabled={!isValid}
89+
splitButtonItems={
90+
isEditMode
91+
? [
92+
<EditSaveButton
93+
key='wizard-edit-save-btn'
94+
getBlueprintPayload={getBlueprintPayload}
95+
setIsOpen={setIsOpen}
96+
blueprintId={blueprintId || ''}
97+
isDisabled={!isValid}
98+
/>,
99+
]
100+
: [
101+
<CreateSaveButton
102+
key='wizard-create-save-btn'
103+
getBlueprintPayload={getBlueprintPayload}
104+
setIsOpen={setIsOpen}
105+
isDisabled={!isValid}
106+
/>,
107+
]
108+
}
109+
/>
110+
)}
111+
onSelect={() => setIsOpen(false)}
112+
>
113+
{isEditMode ? (
114+
<EditSaveAndBuildBtn
115+
getBlueprintPayload={getBlueprintPayload}
116+
blueprintId={blueprintId || ''}
117+
setIsOpen={setIsOpen}
118+
isDisabled={!isValid}
119+
/>
120+
) : (
121+
<CreateSaveAndBuildBtn
122+
getBlueprintPayload={getBlueprintPayload}
123+
setIsOpen={setIsOpen}
124+
isDisabled={!isValid}
125+
/>
126+
)}
127+
</Dropdown>
128+
<Button variant='secondary' onClick={goToPrevStep}>
129+
Back
130+
</Button>
131+
<Button variant='link' onClick={close}>
132+
Cancel
133+
</Button>
134+
</Flex>
135+
</WizardFooterWrapper>
136+
);
137+
};
138+
139+
export default ReviewWizardFooter;

0 commit comments

Comments
 (0)