Skip to content

Commit b80de6a

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

5 files changed

Lines changed: 177 additions & 100 deletions

File tree

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

Lines changed: 8 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 { Form, Icon, InfoMessage, Stack, useToast } 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,19 @@ export const ConfigureStep = () => {
155155
</Stack>
156156
}
157157
>
158-
<SourceSection />
158+
<InfoMessage
159+
title="Cross-Region location"
160+
content="A location is created here, on the source site, pointing to a destination site. The destination automatically receives the resources required for replication."
161+
link="/artesca/docs/data_management/location_management/add_a_crr_location.html"
162+
linkText="Learn more"
163+
/>
159164
<DestinationConnectionSection
160165
isCheckingConnection={verify.isLoading}
161166
onCheckConnection={onCheckConnection}
162167
isConnected={isConnected}
163168
connectedInstanceName={connectedInstanceName}
164169
/>
170+
<SourceSection />
165171
<DestinationAccountSection />
166172
<ReplicationSection />
167173
</Form>

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

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,45 @@
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+
shouldTouch: true,
38+
})
39+
}
40+
/>
41+
</Stack>
42+
}
2343
/>
2444
</FormSection>
2545
);

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

Lines changed: 93 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,22 @@
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+
16+
const ConstrainedInput = styled(Input)`
17+
max-width: 22rem;
18+
`;
19+
820
type Props = {
921
isCheckingConnection: boolean;
1022
onCheckConnection: () => void;
@@ -37,73 +49,94 @@ export const DestinationConnectionSection = ({
3749

3850
return (
3951
<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' && (
52+
<ConnectionBox>
6653
<FormGroup
67-
id="url"
54+
id="connectionMode"
6855
direction="horizontal"
69-
label="URL"
56+
label="Mode"
7057
required
7158
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')}
8459
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')}
60+
<Controller
61+
name="connectionMode"
62+
control={control}
63+
render={({ field }) => (
64+
<RadioGroup
65+
name="connectionMode"
66+
options={[
67+
{
68+
value: 'management-network',
69+
label: 'Management Network',
70+
description: 'Connects directly to the management IP.',
71+
},
72+
{
73+
value: 'data-network',
74+
label: 'Data Network',
75+
description: 'Goes through the public S3 endpoint instead.',
76+
},
77+
]}
78+
value={field.value}
79+
onChange={(next) => field.onChange(next)}
80+
direction="vertical"
81+
/>
82+
)}
10383
/>
10484
}
10585
/>
106-
)}
86+
{connectionMode === 'management-network' && (
87+
<FormGroup
88+
id="url"
89+
direction="horizontal"
90+
label="URL"
91+
required
92+
helpErrorPosition="bottom"
93+
error={errorIfTouched('url')}
94+
content={
95+
<ConstrainedInput id="url" noPlaceholderPrefix placeholder="https://<IP>:8443" {...register('url')} />
96+
}
97+
/>
98+
)}
99+
{connectionMode === 'data-network' && (
100+
<FormGroup
101+
id="baseDomain"
102+
direction="horizontal"
103+
label="Base Domain"
104+
required
105+
helpErrorPosition="bottom"
106+
error={errorIfTouched('baseDomain')}
107+
content={
108+
<ConstrainedInput
109+
id="baseDomain"
110+
noPlaceholderPrefix
111+
placeholder="ui.<base-domain>"
112+
{...register('baseDomain')}
113+
/>
114+
}
115+
/>
116+
)}
117+
{connectionMode === 'data-network' && (
118+
<FormGroup
119+
id="s3Endpoint"
120+
direction="horizontal"
121+
label="S3 Endpoint"
122+
required
123+
helpErrorPosition="bottom"
124+
error={errorIfTouched('s3Endpoint')}
125+
content={
126+
<ConstrainedInput
127+
id="s3Endpoint"
128+
noPlaceholderPrefix
129+
placeholder="https://s3.example.com"
130+
{...register('s3Endpoint')}
131+
/>
132+
}
133+
/>
134+
)}
135+
</ConnectionBox>
136+
<Text color="textSecondary">
137+
Credentials: these must belong to a user with at least the Storage Manager role on the destination site
138+
deployment.
139+
</Text>
107140
<FormGroup
108141
id="username"
109142
direction="horizontal"

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

Lines changed: 44 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,23 @@
11
import { Checkbox, FormGroup, FormSection } from '@scality/core-ui';
22
import { Input } from '@scality/core-ui/dist/next';
33
import { useFormContext } from 'react-hook-form';
4+
import styled from 'styled-components';
45
import type { ConfigureFormValues } from './schema';
56

7+
const RevealFields = styled.div`
8+
@keyframes crr-reveal {
9+
from {
10+
opacity: 0;
11+
transform: translateY(-4px);
12+
}
13+
to {
14+
opacity: 1;
15+
transform: translateY(0);
16+
}
17+
}
18+
animation: crr-reveal 0.2s ease;
19+
`;
20+
621
export const ReplicationSection = () => {
722
const {
823
register,
@@ -19,40 +34,39 @@ export const ReplicationSection = () => {
1934
id="createReplicationRule"
2035
direction="horizontal"
2136
label="Create Replication Rule"
37+
help="Optional — creating a rule now is not required, it can also be set up later from the bucket."
2238
helpErrorPosition="bottom"
2339
content={<Checkbox id="createReplicationRule" {...register('createReplicationRule')} />}
2440
/>
2541
{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-
/>
42+
<RevealFields>
43+
<FormGroup
44+
id="sourceBucketName"
45+
direction="horizontal"
46+
label="Source Bucket name"
47+
required
48+
helpErrorPosition="bottom"
49+
error={errorIfTouched('sourceBucketName')}
50+
content={<Input id="sourceBucketName" autoComplete="off" {...register('sourceBucketName')} />}
51+
/>
52+
<FormGroup
53+
id="targetBucketName"
54+
direction="horizontal"
55+
label="Target Bucket name"
56+
required
57+
helpErrorPosition="bottom"
58+
error={errorIfTouched('targetBucketName')}
59+
content={<Input id="targetBucketName" autoComplete="off" {...register('targetBucketName')} />}
60+
/>
61+
<FormGroup
62+
id="prefix"
63+
direction="horizontal"
64+
label="Prefix (optional)"
65+
helpErrorPosition="bottom"
66+
error={errorIfTouched('prefix')}
67+
content={<Input id="prefix" autoComplete="off" {...register('prefix')} />}
68+
/>
69+
</RevealFields>
5670
)}
5771
</FormSection>
5872
);

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)