diff --git a/src/AppContext.tsx b/src/AppContext.tsx index ed5c41b..7e9291d 100644 --- a/src/AppContext.tsx +++ b/src/AppContext.tsx @@ -1,20 +1,62 @@ import { createContext } from 'react'; import { Domain } from './Api'; +import { VerifyState } from './Routes/WizardPage/Components/VerifyRegistry/VerifyRegistry'; +/** + * It represents the application context so common events and properties + * are shared for many components, making their values accessible. + * @public + */ export interface IAppContext { + /** Represent the current list of domains to be displayed in the listDomains view. */ domains: Domain[]; + /** Callback to set the value of `domains`. */ setDomains: (domains: Domain[]) => void; - // wizardDomain?: Domain; - // setWizardDomain: (domain?: Domain) => void; + /** Encapsulates the context related with the wizard. */ + wizard: { + /** Retrieve the current token, required to register a domain. */ + getToken: () => string; + /** Set the value of the token. */ + setToken: (value: string) => void; + /** Retrieve the value of the registered status which is updated once + * the user has registered the domain by using ipa-hcc tool. */ + getRegisteredStatus: () => VerifyState; + /** Setter for the registered status. */ + setRegisteredStatus: (value: VerifyState) => void; + /** Get the ephemeral domain state that manage the wizard. */ + getDomain: () => Domain; + /** Set the ephemeral domain information. */ + setDomain: (value: Domain) => void; + }; } +/** + * Represent the application context. + * @public + */ export const AppContext = createContext({ domains: [], setDomains: (domains: Domain[]) => { throw new Error('Function "setDomains" not implemented: domains=' + domains); }, - // wizardDomain: undefined, - // setWizardDomain: (domain?: Domain) => { - // throw new Error('Function "setWizardDomain" not implemented: domain=' + domain); - // }, + wizard: { + getToken: (): string => { + return ''; + }, + setToken: (value: string) => { + throw new Error('Function "setToken" not implemented: value=' + value); + }, + getRegisteredStatus: (): VerifyState => { + return 'initial'; + }, + setRegisteredStatus: (value: VerifyState) => { + throw new Error('Function "setRegisteredStatus" not implemented: value=' + value); + }, + getDomain: (): Domain => { + return {} as Domain; + }, + setDomain: (value: Domain) => { + throw new Error('Function "setDomain" not implemented: value=' + value); + }, + }, }); diff --git a/src/AppEntry.tsx b/src/AppEntry.tsx index 8bf2bf4..557347b 100644 --- a/src/AppEntry.tsx +++ b/src/AppEntry.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useContext, useState } from 'react'; import { BrowserRouter as Router } from 'react-router-dom'; import { Provider } from 'react-redux'; import { init } from './store'; @@ -7,16 +7,53 @@ import { getBaseName } from '@redhat-cloud-services/frontend-components-utilitie import logger from 'redux-logger'; import { AppContext } from './AppContext'; import { Domain } from './Api'; +import { VerifyState } from './Routes/WizardPage/Components/VerifyRegistry/VerifyRegistry'; const AppEntry = () => { + const appContext = useContext(AppContext); const [domains, setDomains] = useState([]); + const [wizardToken, setWizardToken] = useState(''); + const [wizardDomain, setWizardDomain] = useState({} as Domain); + const [wizardRegisterStatus, setWizardRegisterStatus] = useState('initial'); const cbSetDomains = (domains: Domain[]) => { + appContext.domains = domains; setDomains(domains); }; + const cbGetWizardToken = (): string => { + return wizardToken; + }; + const cbSetWizardToken = (value: string) => { + setWizardToken(value); + }; + const cbGetWizardDomain = (): Domain => { + return wizardDomain; + }; + const cbSetWizardDomain = (value: Domain) => { + setWizardDomain(value); + }; + const cbGetRegisterStatus = (): VerifyState => { + return wizardRegisterStatus; + }; + const cbSetRegisterStatus = (value: VerifyState) => { + setWizardRegisterStatus(value); + }; return ( - + diff --git a/src/Routes/DefaultPage/DefaultPage.scss b/src/Routes/DefaultPage/DefaultPage.scss index 5624387..acd9576 100644 --- a/src/Routes/DefaultPage/DefaultPage.scss +++ b/src/Routes/DefaultPage/DefaultPage.scss @@ -1,5 +1 @@ @import '~@redhat-cloud-services/frontend-components-utilities/styles/variables'; - -.empty-state-body-content { - margin-bottom: $ins-margin; -} diff --git a/src/Routes/DefaultPage/DefaultPage.tsx b/src/Routes/DefaultPage/DefaultPage.tsx index bb41404..a818229 100644 --- a/src/Routes/DefaultPage/DefaultPage.tsx +++ b/src/Routes/DefaultPage/DefaultPage.tsx @@ -51,6 +51,8 @@ const Header = () => { }; const EmptyContent = () => { + // FIXME Update this link in the future + const linkLearnMoreAbout = 'https://access.redhat.com/articles/1586893'; const navigate = useNavigate(); const handleOpenWizard = () => { @@ -62,21 +64,34 @@ const EmptyContent = () => {
- - - No domains + <RegistryIcon className="pf-u-color-200" size="xl" /> + <Title headingLevel="h2" size="lg" className="pf-u-pt-sm"> + No directory and domain services registered - - To specify which existing access controls can be -
leveraged for hosts launched through the console, you -
must first register your domain(s). As part of that -
process you will be required to SSO into your server(s) -
and install packages via CLI. + + Use access controls from your existing IdM hosts in your cloud +
environment*. To get started, register a service. +
+ + + + + - - + + *Directory and domain services are currently available only for Red Hat IdM and IPA deployments.
@@ -205,10 +220,6 @@ const DefaultPage = () => { const [perPage] = useState(10); const [offset, setOffset] = useState(0); - useEffect(() => { - insights?.chrome?.appAction?.('default-page'); - }, []); - console.log('INFO:DefaultPage render'); useEffect(() => { diff --git a/src/Routes/WizardPage/Components/PagePreparation/PagePreparation.scss b/src/Routes/WizardPage/Components/PagePreparation/PagePreparation.scss index 219c002..acd9576 100644 --- a/src/Routes/WizardPage/Components/PagePreparation/PagePreparation.scss +++ b/src/Routes/WizardPage/Components/PagePreparation/PagePreparation.scss @@ -1,9 +1 @@ @import '~@redhat-cloud-services/frontend-components-utilities/styles/variables'; - -.domain-type-select { - width: 50%; -} - -.domain-item-margin-left { - margin-left: 16px; -} diff --git a/src/Routes/WizardPage/Components/PagePreparation/PagePreparation.tsx b/src/Routes/WizardPage/Components/PagePreparation/PagePreparation.tsx index 511024a..a296657 100644 --- a/src/Routes/WizardPage/Components/PagePreparation/PagePreparation.tsx +++ b/src/Routes/WizardPage/Components/PagePreparation/PagePreparation.tsx @@ -1,26 +1,81 @@ -import React from 'react'; +import React, { useContext, useEffect, useState } from 'react'; import ExternalLinkAltIcon from '@patternfly/react-icons/dist/esm/icons/external-link-alt-icon'; import InfoCircleIcon from '@patternfly/react-icons/dist/esm/icons/info-circle-icon'; -import { Button, ClipboardCopy, Form, FormGroup, Icon, Select, SelectOption, Stack, TextContent } from '@patternfly/react-core'; +import { Button, ClipboardCopy, Form, FormGroup, Icon, Select, SelectOption, TextContent, Title } from '@patternfly/react-core'; import './PagePreparation.scss'; +import { DomainType, ResourcesApiFactory } from '../../../../Api'; +import { AppContext, IAppContext } from '../../../../AppContext'; -const PagePreparation: React.FC = () => { - // TODO Update links - const firewallConfigurationLink = - 'https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/configuring_and_managing_networking/using-and-configuring-firewalld_configuring-and-managing-networking'; - const cloudProviderConfigurationLink = - 'https://access.redhat.com/documentation/es-es/red_hat_subscription_management/2023/html-single/red_hat_cloud_access_reference_guide/index'; - const networkConfigurationLink = 'https://www.redhat.com/sysadmin/network-interface-linux'; - const installServerPackagesLink = 'https://freeipa.org/page/Quick_Start_Guide'; +/** Represent the properties for PagePreparation component. */ +interface PagePreparationProps { + token?: string; + onToken?: (token: string, domain_id: string, expiration: number) => void; +} + +/** + * This page provide information and links to prepare the rhel-idm + * servers before the user could proceed to the registration process. + * @param props provide the token value and the onToken event to + * notify when it is created. + * @returns Return the view to inform the user how to prepare for + * the domain registration. + * @public + * @see {@link PagePreparationProps} to know about the properties. + * @see {@link WizardPage} to know about the parent component. + */ +const PagePreparation = (props: PagePreparationProps) => { + // FIXME Update the target link when it is known + const installServerPackagesLink = 'https://duckduckgo.com/?q=freeipa+prerequisites'; + // FIXME Update the target link when it is known + const prerequisitesLink = 'https://www.google.com?q=rhel-idm+pre-requisites'; // States - const [isOpen, setIsOpen] = React.useState(false); + const [isOpen, setIsOpen] = useState(false); + const appContext = useContext(AppContext); + + const base_url = '/api/idmsvc/v1'; + const resources_api = ResourcesApiFactory(undefined, base_url, undefined); + + const token = appContext.wizard.getToken(); + const domain = appContext.wizard.getDomain(); + const domain_id = domain.domain_id ? domain.domain_id : ''; + + /** + * side effect to retrieve a token in the background. + * TODO When more than one type of domain, this callback will + * invoke from 'onRegisterDomainTypeSelect' event. + */ + useEffect(() => { + if (token != '' && domain_id != '') { + return; + } + // NOTE await and async cannot be used directly because EffectCallback cannot be a Promise + resources_api + .createDomainToken({ domain_type: 'rhel-idm' }, undefined, undefined) + .then((value) => { + appContext.wizard.setToken(value.data.domain_token); + const domain_id = value.data.domain_id; + const domain_name = 'My domain'; + const domain_type = value.data.domain_type; + const token = value.data.domain_token; + const expiration = value.data.expiration; + appContext.wizard.setDomain({ + domain_id: domain_id, + domain_name: domain_name, + domain_type: domain_type, + }); + props.onToken?.(token, domain_id, expiration); + }) + .catch((reason) => { + // FIXME handle the error here + console.log('error creating the token by createDomainToken: ' + reason); + }); + }, [token, domain_id]); - // hooks const onRegisterDomainTypeSelect = () => { - // TODO Not implemented - console.debug('onRegisterDomainTypeSelect in WizardPage'); + // TODO Not implemented, currently only support rhel-idm + console.debug('PagePreparation.onRegisterDomainTypeSelect in WizardPage'); return; }; @@ -30,29 +85,29 @@ const PagePreparation: React.FC = () => { const domainOptions = [ { - value: 'rhel-idm', - title: 'Red Hat Enterprise Linux IdM/IPA', + value: 'rhel-idm' as DomainType, + title: 'RHEL IdM (IPA)', }, ]; return ( - + <> + Preparation for your directory and domain service
{ - console.debug('onSubmit WizardPage' + String(value)); + console.debug('TODO onSubmit WizardPage' + String(value)); }} > - - - Only Red Hat Linux IdM/IPA are currently supported. + {' '} + Only RHEL IdM (IPA) are currently supported. } > @@ -63,7 +118,7 @@ const PagePreparation: React.FC = () => { onSelect={onRegisterDomainTypeSelect} // onOpenChange={(isOpen) => setIsOpen(isOpen)} onToggle={onToggleClick} - className="domain-type-select" + className="pf-u-w-100 pf-u-w-50-on-md pf-u-w-50-on-xl" > {domainOptions.map((option) => ( @@ -72,57 +127,10 @@ const PagePreparation: React.FC = () => { ))} - - - There are prerequisites that must be completed to create and use security for Red Hat Linux IdM/IPA. If any prerequisites are already in - place, please skip to the next step: - - -
- -
- -
- - 4. Verify wether or not the package is present on your server(st) with this command: - - dnf list installed ipa-hcc-server - - - If the package is not present on your server(s), follow these steps:{' '} + +
    +
  1. + Complete the{' '} +
  2. +
  3. + Verify whether or not the package is present on your server(s) with this command: - dnf copr enable copr.devel.redhat.com/cheimes/ipa-hcc && dnf install ipa-hcc-server + dnf list installed ipa-hcc-server - The package must be installed on at least one IPA server. For redundency, the package should be installed on two or more IPA servers, - possibly all IPA servers. - - +
  4. +
  5. + + If the package is not present on your server(s), follow these{' '} + + + dnf copr enable copr.devel.redhat.com/cheimes/ipa-hcc && dnf install ipa-hcc-server + + The package must be installed on at least one IPA server. For redundency, the package should be installed on two or more IPA servers, + possibly all IPA servers. + +
  6. +
-
+ ); }; diff --git a/src/Routes/WizardPage/Components/VerifyRegistry/VerifyRegistry.scss b/src/Routes/WizardPage/Components/VerifyRegistry/VerifyRegistry.scss new file mode 100644 index 0000000..acd9576 --- /dev/null +++ b/src/Routes/WizardPage/Components/VerifyRegistry/VerifyRegistry.scss @@ -0,0 +1 @@ +@import '~@redhat-cloud-services/frontend-components-utilities/styles/variables'; diff --git a/src/Routes/WizardPage/Components/VerifyRegistry/VerifyRegistry.test.tsx b/src/Routes/WizardPage/Components/VerifyRegistry/VerifyRegistry.test.tsx new file mode 100644 index 0000000..ad7d07b --- /dev/null +++ b/src/Routes/WizardPage/Components/VerifyRegistry/VerifyRegistry.test.tsx @@ -0,0 +1,10 @@ +// import React from 'react'; +// import { render } from '@testing-library/react'; +// import Page1 from './Page1'; +import '@testing-library/jest-dom'; + +test('expect VerifyRegistry to render children', () => { + // render(); + // expect(screen.getByRole('heading')).toHaveTextContent('Hello'); + return; +}); diff --git a/src/Routes/WizardPage/Components/VerifyRegistry/VerifyRegistry.tsx b/src/Routes/WizardPage/Components/VerifyRegistry/VerifyRegistry.tsx new file mode 100644 index 0000000..90305a1 --- /dev/null +++ b/src/Routes/WizardPage/Components/VerifyRegistry/VerifyRegistry.tsx @@ -0,0 +1,263 @@ +import React, { useEffect, useState } from 'react'; + +import { AxiosError } from 'axios'; +import { Button, Icon, Stack, StackItem, TextContent } from '@patternfly/react-core'; +import { CheckCircleIcon } from '@patternfly/react-icons/dist/esm/icons/check-circle-icon'; +import { ExclamationCircleIcon } from '@patternfly/react-icons/dist/esm/icons/exclamation-circle-icon'; +import { PendingIcon } from '@patternfly/react-icons/dist/esm/icons/pending-icon'; +import { ExternalLinkAltIcon } from '@patternfly/react-icons/dist/esm/icons/external-link-alt-icon'; + +import './VerifyRegistry.scss'; +import { Domain, ResourcesApiFactory } from '../../../../Api'; + +/* Common definitions */ + +export type VerifyState = 'initial' | 'waiting' | 'timed-out' | 'not-found' | 'completed'; + +/* VerifyRegistryIcon component */ + +interface VerifyRegistryIconProps { + state: VerifyState; +} + +const VerifyRegistryIcon = (props: VerifyRegistryIconProps) => { + return ( + <> + {props.state == 'initial' && ( + + + + )} + {props.state == 'waiting' && ( + + + + )} + {props.state == 'timed-out' && ( + + + + )} + {props.state == 'not-found' && ( + + + + )} + {props.state == 'completed' && ( + + + + )} + + ); +}; + +/* VerifyRegistryLabel component */ + +interface VerifyRegistryLabelProps { + state: VerifyState; +} + +const VerifyRegistryLabel = (props: VerifyRegistryLabelProps) => { + return ( + <> + {props.state == 'initial' && Verify registration} + {props.state == 'waiting' && Verify registration} + {props.state == 'timed-out' && Verify registration} + {props.state == 'not-found' && Verify registration} + {props.state == 'completed' && Verify registration} + + ); +}; + +/* VerifyRegistryLabel component */ + +interface VerifyRegistryDescriptionProps { + state: VerifyState; +} + +const VerifyRegistryDescription = (props: VerifyRegistryDescriptionProps) => { + return ( + <> + {props.state == 'initial' && Running verification test} + {props.state == 'waiting' && Waiting for registration data} + {props.state == 'timed-out' && Test timed out} + {props.state == 'not-found' && Registration data not found} + {props.state == 'completed' && Test completed} + + ); +}; + +/* VerifyRegistryyFooter */ + +interface VerifyRegistryFooterProps { + state: VerifyState; + onTest?: () => void; +} + +const VerifyRegistryFooter = (props: VerifyRegistryFooterProps) => { + const linkTroubleshootRegistration = 'https://www.google.com/search?q=freeipa+troubleshooting'; + return ( + <> + {props.state == 'initial' && ( + <> + + + )} + {props.state == 'waiting' && <>} + {props.state == 'timed-out' && ( + <> + + + )} + {props.state == 'not-found' && ( + <> + + + )} + {props.state == 'completed' && ( + <> + + + )} + + ); +}; + +/* VerifyRegistry component */ + +interface VerifyRegistryProps { + state: VerifyState; + uuid: string; + onChange: (newState: VerifyState, domain?: Domain) => void; +} + +const VerifyRegistry = (props: VerifyRegistryProps) => { + const [isPolling, setIsPolling] = useState(true); + + const base_url = '/api/idmsvc/v1'; + const resources_api = ResourcesApiFactory(undefined, base_url, undefined); + const timeout = 3 * 1000; // Seconds + + /** TODO Extract this effect in a hook to simplify this code */ + useEffect(() => { + let intervalId: NodeJS.Timeout | null = null; + let elapsedTime = 0; + let newState: VerifyState = props.state; + let domain: Domain | undefined = undefined; + const stopPolling = (state: VerifyState, domain?: Domain) => { + if (intervalId) { + clearInterval(intervalId); + } + intervalId = null; + setIsPolling(false); + if (state === 'completed') { + props.onChange(state, domain); + } else { + props.onChange(state); + } + }; + if (!isPolling) { + return; + } + const fetchData = async () => { + try { + const response = await resources_api.readDomain(props.uuid, undefined, undefined); + newState = 'completed'; + domain = response.data; + newState = 'completed'; + } catch (error) { + const axiosError = error as AxiosError; + switch (axiosError.code) { + case AxiosError.ECONNABORTED: + case AxiosError.ERR_BAD_OPTION: + case AxiosError.ERR_BAD_OPTION_VALUE: + case AxiosError.ERR_CANCELED: + newState = 'waiting'; + break; + case AxiosError.ERR_DEPRECATED: + case AxiosError.ERR_FR_TOO_MANY_REDIRECTS: + case AxiosError.ERR_NETWORK: + case AxiosError.ETIMEDOUT: + newState = 'timed-out'; + break; + case AxiosError.ERR_BAD_REQUEST: + case AxiosError.ERR_BAD_RESPONSE: + default: + newState = 'waiting'; + break; + } + } + + if (newState !== undefined && newState !== props.state) { + switch (newState) { + case 'timed-out': + case 'waiting': + props.onChange(newState); + // setState(newState); + break; + default: + if (elapsedTime >= timeout) { + newState = 'timed-out'; + stopPolling(newState); + } + break; + case 'completed': + stopPolling(newState, domain); + break; + } + } + elapsedTime += 1000; // Increase elapsed time by 1 second + if (elapsedTime > timeout) { + newState = 'timed-out'; + stopPolling(newState); + } + }; + + fetchData(); + intervalId = setInterval(fetchData, 1000); + + return () => { + if (intervalId) { + clearInterval(intervalId); + } + }; + }, [isPolling]); + + const onRetry = () => { + props.onChange('initial'); + // setState('initial'); + setIsPolling(true); + }; + + return ( + + + + + + + + + + + + + + + + + ); +}; + +// VerifyRegistry.defaultProps = defaultVerifyRegistryProps; + +export default VerifyRegistry; diff --git a/src/Routes/WizardPage/WizardPage.tsx b/src/Routes/WizardPage/WizardPage.tsx index a8edb05..7699ed1 100644 --- a/src/Routes/WizardPage/WizardPage.tsx +++ b/src/Routes/WizardPage/WizardPage.tsx @@ -1,13 +1,20 @@ -import React, { useEffect, useState } from 'react'; +/** + * This library implement the WizardPage. + * + * The goal is provide the steps to register and add + * a new domain service. + */ +import React, { useContext, useState } from 'react'; import { ExternalLinkAltIcon } from '@patternfly/react-icons/dist/esm/icons/external-link-alt-icon'; -import { Button, Page, PageSection, PageSectionTypes, PageSectionVariants, Wizard } from '@patternfly/react-core'; +import { Button, Page, PageSection, PageSectionTypes, PageSectionVariants, Wizard, WizardStep } from '@patternfly/react-core'; import { PageHeader, PageHeaderTitle } from '@redhat-cloud-services/frontend-components/PageHeader'; import './WizardPage.scss'; import { useNavigate } from 'react-router-dom'; -import { Domain } from '../../Api/api'; +import { Domain, ResourcesApiFactory } from '../../Api/api'; +import { AppContext } from '../../AppContext'; // Lazy load for the wizard pages const PagePreparation = React.lazy(() => import('./Components/PagePreparation/PagePreparation')); @@ -33,57 +40,119 @@ const initialDomain: Domain = { /** * Wizard page to register a new domain into the service. + * @see {@link PagePreparation} about the preparation page. + * @see {@link PageServiceRegistration} about the registration page. + * @see {@link PageServiceDetails} about the details page. + * @see {@link PageReview} about the review page. */ -const WizardPage: React.FC = () => { +const WizardPage = () => { + const base_url = '/api/idmsvc/v1'; + const resources_api = ResourcesApiFactory(undefined, base_url, undefined); + const appContext = useContext(AppContext); + const domain = appContext.wizard.getDomain(); const navigate = useNavigate(); - // TODO Update the initial state into the context so that - // state can be read/write from the different pages - // into the wizard process. - const [data] = useState(initialDomain); - - useEffect(() => { - insights?.chrome?.appAction?.('default-page'); - }, []); - // FIXME Update the URL with the location for docs const linkLearnMoreAbout = 'https://access.redhat.com/articles/1586893'; - // Event when Close button is clicked + /** Event triggered when Close button is clicked. */ const onCloseClick = () => { - // TODO Not implemented - // What happens on Cancel? (see documentation) + // FIXME A few things pending: + // - Mocal confirmation + // - Confirm => + // - if not registered, dismiss wizard + // - else => DELETE /domains/:domain_id + // - Cancel or close model => Do not dismiss wizard navigate('/domains'); }; + /** Event triggered when Back button is clicked. */ + const onPreviousPage = ( + _newStep: { id?: string | number; name: React.ReactNode }, + _prevStep: { prevId?: string | number; prevName: React.ReactNode } + ) => { + console.log('onPreviousPage fired'); + return; + }; + + /** Event triggered when a specific page is clicked. */ + const onGoToStep = ( + _newStep: { id?: string | number; name: React.ReactNode }, + _prevStep: { prevId?: string | number; prevName: React.ReactNode } + ) => { + console.log('onGoToStep fired'); + return; + }; + + /** Event triggered when the Next button is clicked. */ + const onNextPage = async ({ id }: WizardStep) => { + // FIXME Delete log + console.log('onNextPage fired for id=' + id); + if (id === undefined) { + return; + } + if (typeof id === 'string') { + const [, orderIndex] = id.split('-'); + id = parseInt(orderIndex); + } + }; + + const initCanJumpPage1 = true; + const initCanJumpPage2 = initCanJumpPage1 && domain.domain_id != '' && appContext.wizard.getToken() != ''; + const initCanJumpPage3 = initCanJumpPage2; + const initCanJumpPage4 = initCanJumpPage3 && domain.title !== undefined && domain.title.length > 0; + + const [canJumpPage1] = useState(initCanJumpPage1); + const [canJumpPage2, setCanJumpPage2] = useState(initCanJumpPage2); + const [canJumpPage3, setCanJumpPage3] = useState(initCanJumpPage3); + const [canJumpPage4, setCanJumpPage4] = useState(initCanJumpPage4); + + const onToken = (token: string, domain_id: string, expiration: number) => { + console.log('WizardPage.OnToken fired: token=' + token + '; domain_id=' + domain_id + '; expiration=' + expiration); + if (token != '') { + setCanJumpPage2(true); + } else { + setCanJumpPage2(false); + } + }; + + /** Configure the wizard pages. */ const steps = [ { // This page only display the pre-requisites + id: 1, name: 'Preparation', - component: , + component: , + canJumpTo: canJumpPage1, }, { + id: 2, name: 'Service registration', // FIXME Pass here the 'registering.domain' field from the context // FIXME Pass here the 'registering.token' field from the context - component: , + component: , + canJumpTo: canJumpPage2, }, { + id: 3, name: 'Service details', // FIXME Pass here the 'registering.domain' field from the context - component: , + component: , + canJumpTo: canJumpPage3, }, { + id: 4, name: 'Review', // FIXME Pass here the 'registering.domain' field from the context - component: , + component: , + canJumpTo: canJumpPage4, }, ]; const title = 'Register directory and domain service'; return ( - + <> @@ -98,7 +167,7 @@ const WizardPage: React.FC = () => { iconPosition="right" href={linkLearnMoreAbout} > - Learn more about the directory and domain services. + Learn more about the directory and domain services{' '}

@@ -106,7 +175,7 @@ const WizardPage: React.FC = () => {
-
+ ); };