Skip to content

Commit 7863f8a

Browse files
committed
improvement(crr): redesign the Configure Location form per UX review
1 parent 03ca9b8 commit 7863f8a

5 files changed

Lines changed: 185 additions & 102 deletions

File tree

src/react/locations/CRRSetupWizard/steps/ConfigureStep/ConfigureStep.tsx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Form, Icon, Stack, useToast } from '@scality/core-ui';
1+
import { Banner, Form, Icon, Link, Stack, Text, useToast, Wrap } from '@scality/core-ui';
22
import { useStepper } from '@scality/core-ui/dist/components/steppers/Stepper.component';
33
import { Button } from '@scality/core-ui/dist/next';
44
import { useBasenameRelativeNavigate } from '@scality/module-federation';
@@ -155,13 +155,27 @@ export const ConfigureStep = () => {
155155
</Stack>
156156
}
157157
>
158-
<SourceSection />
158+
<Banner variant="base" title="Cross-Region location" icon={<Icon name="Info-circle" />}>
159+
<Stack direction="vertical" gap="r8">
160+
<Text>
161+
A location is created here, on the source site, pointing to a destination site. The destination
162+
automatically receives the resources required for replication.
163+
</Text>
164+
<Wrap width="100%">
165+
<div />
166+
<Link href="/artesca/docs/data_management/location_management/add_a_crr_location.html" target="_blank">
167+
Learn more
168+
</Link>
169+
</Wrap>
170+
</Stack>
171+
</Banner>
159172
<DestinationConnectionSection
160173
isCheckingConnection={verify.isLoading}
161174
onCheckConnection={onCheckConnection}
162175
isConnected={isConnected}
163176
connectedInstanceName={connectedInstanceName}
164177
/>
178+
<SourceSection />
165179
<DestinationAccountSection />
166180
<ReplicationSection />
167181
</Form>

src/react/locations/CRRSetupWizard/steps/ConfigureStep/DestinationAccountSection.tsx

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,44 @@
1-
import { FormGroup, FormSection } from '@scality/core-ui';
2-
import { Input } from '@scality/core-ui/dist/next';
1+
import { FormGroup, FormSection, Icon, Stack, Text } from '@scality/core-ui';
2+
import { Button, Input } from '@scality/core-ui/dist/next';
33
import { useFormContext } from 'react-hook-form';
44
import type { ConfigureFormValues } from './schema';
55

66
export const DestinationAccountSection = () => {
77
const {
88
register,
9+
setValue,
10+
getValues,
911
formState: { errors, touchedFields },
1012
} = useFormContext<ConfigureFormValues>();
1113
const nameError = touchedFields.destinationAccountName ? errors.destinationAccountName?.message : undefined;
1214

1315
return (
14-
<FormSection forceLabelWidth={280} title={{ name: 'Destination Account' }}>
16+
<FormSection forceLabelWidth={280} title={{ name: 'Destination site' }}>
17+
<Text color="textSecondary">An account will be created on the destination site with this name.</Text>
1518
<FormGroup
1619
id="destinationAccountName"
1720
direction="horizontal"
18-
label="Account Name"
21+
label="Account name"
1922
required
2023
helpErrorPosition="bottom"
2124
error={nameError}
22-
content={<Input id="destinationAccountName" autoComplete="off" {...register('destinationAccountName')} />}
25+
content={
26+
<Stack direction="vertical" gap="r8">
27+
<Input id="destinationAccountName" autoComplete="off" {...register('destinationAccountName')} />
28+
<Button
29+
type="button"
30+
variant="outline"
31+
label="Copy from Source site Account name"
32+
icon={<Icon name="Copy" />}
33+
onClick={() =>
34+
setValue('destinationAccountName', getValues('accountName'), {
35+
shouldValidate: true,
36+
shouldDirty: true,
37+
})
38+
}
39+
/>
40+
</Stack>
41+
}
2342
/>
2443
</FormSection>
2544
);

src/react/locations/CRRSetupWizard/steps/ConfigureStep/DestinationConnectionSection.tsx

Lines changed: 97 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
1-
import { FormGroup, FormSection, Icon, Stack, Text, Wrap } from '@scality/core-ui';
1+
import { FormGroup, FormSection, Icon, Stack, spacing, Text, Wrap } from '@scality/core-ui';
22
import { Button, Input } from '@scality/core-ui/dist/next';
33
import { Controller, useFormContext } from 'react-hook-form';
4+
import styled from 'styled-components';
45
import { RadioGroup } from '../../../../ISV/components/RadioGroup';
56
import { CertificateSection } from '../../../../ui-elements/CertificateSection';
67
import type { ConfigureFormValues } from './schema';
78

9+
const ConnectionBox = styled.div`
10+
background: ${(props) => props.theme.backgroundLevel2};
11+
border: 1px solid ${(props) => props.theme.border};
12+
border-radius: 6px;
13+
padding: ${spacing.r16};
14+
`;
15+
816
type Props = {
917
isCheckingConnection: boolean;
1018
onCheckConnection: () => void;
@@ -37,73 +45,102 @@ export const DestinationConnectionSection = ({
3745

3846
return (
3947
<FormSection forceLabelWidth={280} title={{ name: 'Destination Connection' }}>
40-
<FormGroup
41-
id="connectionMode"
42-
direction="horizontal"
43-
label="Mode"
44-
required
45-
helpErrorPosition="bottom"
46-
content={
47-
<Controller
48-
name="connectionMode"
49-
control={control}
50-
render={({ field }) => (
51-
<RadioGroup
52-
name="connectionMode"
53-
options={[
54-
{ value: 'management-network', label: 'Management Network' },
55-
{ value: 'data-network', label: 'Data Network' },
56-
]}
57-
value={field.value}
58-
onChange={(next) => field.onChange(next)}
59-
direction="horizontal"
60-
/>
61-
)}
62-
/>
63-
}
64-
/>
65-
{connectionMode === 'management-network' && (
48+
<ConnectionBox>
6649
<FormGroup
67-
id="url"
50+
id="connectionMode"
6851
direction="horizontal"
69-
label="URL"
52+
label="Mode"
7053
required
7154
helpErrorPosition="bottom"
72-
error={errorIfTouched('url')}
73-
content={<Input id="url" noPlaceholderPrefix placeholder="https://<IP>:8443" {...register('url')} />}
74-
/>
75-
)}
76-
{connectionMode === 'data-network' && (
77-
<FormGroup
78-
id="baseDomain"
79-
direction="horizontal"
80-
label="Base Domain"
81-
required
82-
helpErrorPosition="bottom"
83-
error={errorIfTouched('baseDomain')}
8455
content={
85-
<Input id="baseDomain" noPlaceholderPrefix placeholder="ui.<base-domain>" {...register('baseDomain')} />
86-
}
87-
/>
88-
)}
89-
{connectionMode === 'data-network' && (
90-
<FormGroup
91-
id="s3Endpoint"
92-
direction="horizontal"
93-
label="S3 Endpoint"
94-
required
95-
helpErrorPosition="bottom"
96-
error={errorIfTouched('s3Endpoint')}
97-
content={
98-
<Input
99-
id="s3Endpoint"
100-
noPlaceholderPrefix
101-
placeholder="https://s3.example.com"
102-
{...register('s3Endpoint')}
56+
<Controller
57+
name="connectionMode"
58+
control={control}
59+
render={({ field }) => (
60+
<RadioGroup
61+
name="connectionMode"
62+
options={[
63+
{
64+
value: 'management-network',
65+
label: 'Management Network',
66+
description: 'Connects directly to the management IP.',
67+
},
68+
{
69+
value: 'data-network',
70+
label: 'Data Network',
71+
description: 'Goes through the public S3 endpoint instead.',
72+
},
73+
]}
74+
value={field.value}
75+
onChange={(next) => field.onChange(next)}
76+
direction="vertical"
77+
/>
78+
)}
10379
/>
10480
}
10581
/>
106-
)}
82+
{connectionMode === 'management-network' && (
83+
<FormGroup
84+
id="url"
85+
direction="horizontal"
86+
label="URL"
87+
required
88+
helpErrorPosition="bottom"
89+
error={errorIfTouched('url')}
90+
content={
91+
<Input
92+
id="url"
93+
noPlaceholderPrefix
94+
placeholder="https://<IP>:8443"
95+
style={{ maxWidth: '22rem' }}
96+
{...register('url')}
97+
/>
98+
}
99+
/>
100+
)}
101+
{connectionMode === 'data-network' && (
102+
<FormGroup
103+
id="baseDomain"
104+
direction="horizontal"
105+
label="Base Domain"
106+
required
107+
helpErrorPosition="bottom"
108+
error={errorIfTouched('baseDomain')}
109+
content={
110+
<Input
111+
id="baseDomain"
112+
noPlaceholderPrefix
113+
placeholder="ui.<base-domain>"
114+
style={{ maxWidth: '22rem' }}
115+
{...register('baseDomain')}
116+
/>
117+
}
118+
/>
119+
)}
120+
{connectionMode === 'data-network' && (
121+
<FormGroup
122+
id="s3Endpoint"
123+
direction="horizontal"
124+
label="S3 Endpoint"
125+
required
126+
helpErrorPosition="bottom"
127+
error={errorIfTouched('s3Endpoint')}
128+
content={
129+
<Input
130+
id="s3Endpoint"
131+
noPlaceholderPrefix
132+
placeholder="https://s3.example.com"
133+
style={{ maxWidth: '22rem' }}
134+
{...register('s3Endpoint')}
135+
/>
136+
}
137+
/>
138+
)}
139+
</ConnectionBox>
140+
<Text color="textSecondary">
141+
Credentials: these must belong to a user with at least the Storage Manager role on the destination site
142+
deployment.
143+
</Text>
107144
<FormGroup
108145
id="username"
109146
direction="horizontal"

src/react/locations/CRRSetupWizard/steps/ConfigureStep/ReplicationSection.tsx

Lines changed: 41 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { Checkbox, FormGroup, FormSection } from '@scality/core-ui';
1+
import { Checkbox, FormGroup, FormSection, Text } from '@scality/core-ui';
22
import { Input } from '@scality/core-ui/dist/next';
3+
import { useEffect, useRef } from 'react';
34
import { useFormContext } from 'react-hook-form';
45
import type { ConfigureFormValues } from './schema';
56

@@ -12,9 +13,19 @@ export const ReplicationSection = () => {
1213
const enabled = watch('createReplicationRule');
1314
const errorIfTouched = (field: keyof ConfigureFormValues) =>
1415
touchedFields[field] ? errors[field]?.message : undefined;
16+
const ruleFieldsRef = useRef<HTMLDivElement>(null);
17+
18+
useEffect(() => {
19+
if (enabled) {
20+
ruleFieldsRef.current?.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
21+
}
22+
}, [enabled]);
1523

1624
return (
17-
<FormSection forceLabelWidth={280} title={{ name: 'Replication' }}>
25+
<FormSection forceLabelWidth={280} title={{ name: 'Replication (optional)' }}>
26+
<Text color="textSecondary">
27+
Optional — creating a rule now is not required, it can also be set up later from the bucket.
28+
</Text>
1829
<FormGroup
1930
id="createReplicationRule"
2031
direction="horizontal"
@@ -23,36 +34,34 @@ export const ReplicationSection = () => {
2334
content={<Checkbox id="createReplicationRule" {...register('createReplicationRule')} />}
2435
/>
2536
{enabled && (
26-
<FormGroup
27-
id="sourceBucketName"
28-
direction="horizontal"
29-
label="Source Bucket Name"
30-
required
31-
helpErrorPosition="bottom"
32-
error={errorIfTouched('sourceBucketName')}
33-
content={<Input id="sourceBucketName" autoComplete="off" {...register('sourceBucketName')} />}
34-
/>
35-
)}
36-
{enabled && (
37-
<FormGroup
38-
id="targetBucketName"
39-
direction="horizontal"
40-
label="Target Bucket Name"
41-
required
42-
helpErrorPosition="bottom"
43-
error={errorIfTouched('targetBucketName')}
44-
content={<Input id="targetBucketName" autoComplete="off" {...register('targetBucketName')} />}
45-
/>
46-
)}
47-
{enabled && (
48-
<FormGroup
49-
id="prefix"
50-
direction="horizontal"
51-
label="Prefix (optional)"
52-
helpErrorPosition="bottom"
53-
error={errorIfTouched('prefix')}
54-
content={<Input id="prefix" autoComplete="off" {...register('prefix')} />}
55-
/>
37+
<div ref={ruleFieldsRef}>
38+
<FormGroup
39+
id="sourceBucketName"
40+
direction="horizontal"
41+
label="Source Bucket name"
42+
required
43+
helpErrorPosition="bottom"
44+
error={errorIfTouched('sourceBucketName')}
45+
content={<Input id="sourceBucketName" autoComplete="off" {...register('sourceBucketName')} />}
46+
/>
47+
<FormGroup
48+
id="targetBucketName"
49+
direction="horizontal"
50+
label="Target Bucket name"
51+
required
52+
helpErrorPosition="bottom"
53+
error={errorIfTouched('targetBucketName')}
54+
content={<Input id="targetBucketName" autoComplete="off" {...register('targetBucketName')} />}
55+
/>
56+
<FormGroup
57+
id="prefix"
58+
direction="horizontal"
59+
label="Prefix (optional)"
60+
helpErrorPosition="bottom"
61+
error={errorIfTouched('prefix')}
62+
content={<Input id="prefix" autoComplete="off" {...register('prefix')} />}
63+
/>
64+
</div>
5665
)}
5766
</FormSection>
5867
);

src/react/locations/CRRSetupWizard/steps/ConfigureStep/SourceSection.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { FormGroup, FormSection } from '@scality/core-ui';
1+
import { FormGroup, FormSection, Text } from '@scality/core-ui';
22
import { Input, Select } from '@scality/core-ui/dist/next';
33
import { useMemo } from 'react';
44
import { Controller, useFormContext } from 'react-hook-form';
@@ -42,7 +42,11 @@ export const SourceSection = () => {
4242
);
4343

4444
return (
45-
<FormSection forceLabelWidth={280} title={{ name: 'Source' }}>
45+
<FormSection forceLabelWidth={280} title={{ name: 'Source site' }}>
46+
<Text color="textSecondary">
47+
Use an existing account or create a new one. If you choose an existing account, data already present in its
48+
buckets won't be recovered by the replication.
49+
</Text>
4650
<FormGroup
4751
id="accountNameType"
4852
direction="horizontal"
@@ -70,7 +74,7 @@ export const SourceSection = () => {
7074
<FormGroup
7175
id="accountName"
7276
direction="horizontal"
73-
label="Account Name"
77+
label="Account name"
7478
required
7579
helpErrorPosition="bottom"
7680
error={accountNameError}

0 commit comments

Comments
 (0)