]> source.dussan.org Git - sonarqube.git/blob
e5111eab7021d6b21828d9bc39ab22bcf171972f
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2024 SonarSource SA
4  * mailto:info AT sonarsource DOT com
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 3 of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19  */
20 import {
21   ButtonIcon,
22   ButtonVariety,
23   IconDelete,
24   IconEdit,
25   InputSize,
26   Select,
27 } from '@sonarsource/echoes-react';
28 import { FormField, InputField, TextError } from 'design-system/lib';
29 import { isEmpty, isUndefined } from 'lodash';
30 import React, { useEffect } from 'react';
31 import isEmail from 'validator/lib/isEmail';
32 import { translate, translateWithParameters } from '../../../../helpers/l10n';
33
34 type InputType = 'email' | 'number' | 'password' | 'select' | 'text';
35
36 interface Props {
37   children?: (props: { onChange: (value: string) => void }) => React.ReactNode;
38   description: React.ReactNode;
39   hasValue?: boolean;
40   id: string;
41   name: string;
42   onChange: (value: string | undefined) => void;
43   options?: string[];
44   required?: boolean;
45   requiresRevaluation?: boolean;
46   type?: InputType;
47   value: string | undefined;
48 }
49
50 export function EmailNotificationFormField(props: Readonly<Props>) {
51   const {
52     description,
53     hasValue,
54     id,
55     name,
56     options,
57     required,
58     requiresRevaluation,
59     type = 'text',
60     value,
61   } = props;
62
63   const [validationMessage, setValidationMessage] = React.useState<string>();
64
65   const handleCheck = (changedValue: string | undefined) => {
66     if (changedValue !== undefined && isEmpty(changedValue) && required) {
67       setValidationMessage(translate('settings.state.value_cant_be_empty_no_default'));
68       return false;
69     }
70
71     if (type === 'email' && !isEmail(changedValue ?? '')) {
72       setValidationMessage(translate('email_notification.state.value_should_be_valid_email'));
73       return false;
74     }
75
76     setValidationMessage(undefined);
77     return true;
78   };
79
80   const onChange = (newValue: string | undefined) => {
81     handleCheck(newValue);
82     props.onChange(newValue);
83   };
84
85   const hasValidationMessage = !isUndefined(validationMessage);
86
87   return (
88     <FormField
89       className="sw-grid sw-grid-cols-2 sw-gap-x-4 sw-py-6 sw-px-4"
90       htmlFor={id}
91       label={translate(name)}
92       required={required}
93       requiredAriaLabel={translate('field_required')}
94     >
95       <div className="sw-row-span-2 sw-grid">
96         <EmailInput
97           hasValue={hasValue}
98           id={id}
99           name={name}
100           options={options ?? []}
101           onChange={onChange}
102           required={required}
103           requiresRevaluation={requiresRevaluation}
104           type={type}
105           value={value}
106         />
107
108         {hasValidationMessage && (
109           <TextError
110             className="sw-mt-2"
111             text={translateWithParameters('settings.state.validation_failed', validationMessage)}
112           />
113         )}
114       </div>
115
116       <div className="sw-w-abs-300">
117         {!isUndefined(description) && <div className="markdown sw-mt-1">{description}</div>}
118       </div>
119     </FormField>
120   );
121 }
122
123 function EmailInput(
124   props: Readonly<{
125     hasValue: boolean | undefined;
126     id: string;
127     name: string;
128     onChange: (value: string | undefined) => void;
129     options: string[];
130     required?: boolean;
131     requiresRevaluation?: boolean;
132     type: InputType;
133     value: string | undefined;
134   }>,
135 ) {
136   const { type } = props;
137   switch (type) {
138     case 'password':
139       return <PasswordInput {...props} />;
140     case 'select':
141       return <SelectInput {...props} />;
142     default:
143       return <BasicInput {...props} />;
144   }
145 }
146
147 function BasicInput(
148   props: Readonly<{
149     hasValue: boolean | undefined;
150     id: string;
151     name: string;
152     onChange: (value: string) => void;
153     required?: boolean;
154     type: InputType;
155     value: string | undefined;
156   }>,
157 ) {
158   const { hasValue, id, onChange, name, required, type, value } = props;
159
160   return (
161     <InputField
162       id={id}
163       min={type === 'number' ? 0 : undefined}
164       name={name}
165       onChange={(event) => onChange(event.target.value)}
166       placeholder={
167         type === 'password' && hasValue ? translate('email_notification.form.private') : undefined
168       }
169       required={required}
170       size="large"
171       step={type === 'number' ? 1 : undefined}
172       type={type}
173       value={value ?? ''}
174     />
175   );
176 }
177
178 function PasswordInput(
179   props: Readonly<{
180     hasValue: boolean | undefined;
181     id: string;
182     name: string;
183     onChange: (value: string | undefined) => void;
184     required?: boolean;
185     requiresRevaluation?: boolean;
186     value: string | undefined;
187   }>,
188 ) {
189   const { hasValue, id, onChange, name, required, requiresRevaluation, value } = props;
190   const [isEditing, setIsEditing] = React.useState<boolean>(requiresRevaluation === true);
191
192   useEffect(() => {
193     if (!requiresRevaluation) {
194       setIsEditing(!hasValue);
195     }
196   }, [hasValue, requiresRevaluation]);
197
198   return (
199     <div className="sw-flex">
200       <InputField
201         disabled={!isEditing && !requiresRevaluation}
202         id={id}
203         name={name}
204         onChange={(event) => onChange(event.target.value)}
205         required={isEditing && required}
206         size="large"
207         type="password"
208         value={
209           hasValue && !isEditing && !requiresRevaluation
210             ? translate('email_notification.form.private')
211             : value ?? ''
212         }
213       />
214       {!requiresRevaluation && (
215         <ButtonIcon
216           ariaLabel={isEditing ? translate('reset_verb') : translate('edit')}
217           data-testid={`${name}-${isEditing ? 'reset' : 'edit'}`}
218           className="sw-ml-2"
219           Icon={isEditing ? IconDelete : IconEdit}
220           onClick={() => {
221             if (isEditing) {
222               onChange(undefined);
223             }
224             setIsEditing(!isEditing);
225           }}
226           variety={ButtonVariety.Default}
227         />
228       )}
229     </div>
230   );
231 }
232
233 function SelectInput(
234   props: Readonly<{
235     id: string;
236     name: string;
237     onChange: (value: string) => void;
238     options: string[];
239     required?: boolean;
240     value: string | undefined;
241   }>,
242 ) {
243   const { id, name, onChange, options, required, value } = props;
244
245   return (
246     <Select
247       data={options?.map((option) => ({ label: option, value: option })) ?? []}
248       id={id}
249       isNotClearable
250       isRequired={required}
251       name={name}
252       onChange={onChange}
253       size={InputSize.Large}
254       value={value}
255     />
256   );
257 }