@atlaskit/feedback-collector 14.3.3 → 14.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # @atlaskit/feedback-collector
2
2
 
3
+ ## 14.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`2b6afea21ef37`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/2b6afea21ef37) -
8
+ Move the privacy policy link out of the label element
9
+ - [`fe108a4620163`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/fe108a4620163) -
10
+ Product/App term refresh
11
+
12
+ ## 14.3.4
13
+
14
+ ### Patch Changes
15
+
16
+ - [`94ba8d27dba35`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/94ba8d27dba35) -
17
+ [ux] enable the submit button by default for a11y, validate and prevent submission if required
18
+ fields are not complete
19
+
3
20
  ## 14.3.3
4
21
 
5
22
  ### Patch Changes
@@ -163,7 +163,7 @@ var FeedbackCollector = exports.default = /*#__PURE__*/function (_Component) {
163
163
  }, {
164
164
  key: "getPackageVersion",
165
165
  value: function getPackageVersion() {
166
- return "14.3.2" || 'Unknown, at least 11.0.0';
166
+ return "14.3.4" || 'Unknown, at least 11.0.0';
167
167
  }
168
168
  }, {
169
169
  key: "getEntitlementInformation",
@@ -9,6 +9,7 @@ var _react = _interopRequireDefault(require("react"));
9
9
  var _reactIntlNext = require("react-intl-next");
10
10
  var _flag = require("@atlaskit/flag");
11
11
  var _statusSuccessCheckCircle = _interopRequireDefault(require("@atlaskit/icon/core/migration/status-success--check-circle"));
12
+ var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
12
13
  var _colors = require("@atlaskit/theme/colors");
13
14
  var _messages = require("../messages");
14
15
  var _IntlProviderWithResolvedMessages = require("./IntlProviderWithResolvedMessages");
@@ -24,7 +25,7 @@ var FeedbackFlag = function FeedbackFlag(_ref) {
24
25
  label: "Success"
25
26
  }),
26
27
  id: "feedbackSent",
27
- description: description || formatMessage(_messages.messages.feedbackSuccessFlagDescription),
28
+ description: description || formatMessage((0, _platformFeatureFlags.fg)('product-terminology-refresh') ? _messages.messages.feedbackSuccessFlagDescriptionAppify : _messages.messages.feedbackSuccessFlagDescription),
28
29
  title: title || formatMessage(_messages.messages.feedbackSuccessFlagTitle)
29
30
  });
30
31
  };
@@ -19,6 +19,7 @@ var _checkbox = require("@atlaskit/checkbox");
19
19
  var _form = _interopRequireWildcard(require("@atlaskit/form"));
20
20
  var _link = _interopRequireDefault(require("@atlaskit/link"));
21
21
  var _modalDialog = _interopRequireWildcard(require("@atlaskit/modal-dialog"));
22
+ var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
22
23
  var _sectionMessage = _interopRequireDefault(require("@atlaskit/section-message"));
23
24
  var _select = _interopRequireDefault(require("@atlaskit/select"));
24
25
  var _textarea = _interopRequireDefault(require("@atlaskit/textarea"));
@@ -74,6 +75,10 @@ var FeedbackForm = function FeedbackForm(_ref) {
74
75
  _useState0 = (0, _slicedToArray2.default)(_useState9, 2),
75
76
  isSubmitting = _useState0[0],
76
77
  setIsSubmitting = _useState0[1];
78
+ var _useState1 = (0, _react.useState)({}),
79
+ _useState10 = (0, _slicedToArray2.default)(_useState1, 2),
80
+ validationErrors = _useState10[0],
81
+ setValidationErrors = _useState10[1];
77
82
  var _useIntl = (0, _reactIntlNext.useIntl)(),
78
83
  formatMessage = _useIntl.formatMessage;
79
84
  var isTypeSelected = function isTypeSelected() {
@@ -81,7 +86,26 @@ var FeedbackForm = function FeedbackForm(_ref) {
81
86
  };
82
87
  var canShowTextField = isTypeSelected() || !showTypeField;
83
88
  var hasDescription = description || hasDescriptionDefaultValue;
84
- var isDisabled = disableSubmitButton || (showTypeField ? !isTypeSelected() || !hasDescription : !hasDescription);
89
+
90
+ // Feature flag determines validation behavior
91
+ var useNewValidation = (0, _platformFeatureFlags.fg)('feedback-collector-custom-validation');
92
+ var isDisabled = useNewValidation ? isSubmitting || disableSubmitButton // New: only disable when submitting or explicitly disabled
93
+ : disableSubmitButton || (showTypeField ? !isTypeSelected() || !hasDescription : !hasDescription); // Old: disable based on form validation
94
+
95
+ var getValidationErrors = function getValidationErrors() {
96
+ var errors = {};
97
+
98
+ // Validate type selection if showTypeField is true
99
+ if (showTypeField && !isTypeSelected()) {
100
+ errors.type = formatMessage(_messages.messages.validationErrorTypeRequired);
101
+ }
102
+
103
+ // Validate description if showDefaultTextFields is true
104
+ if (showDefaultTextFields && !hasDescription) {
105
+ errors.description = formatMessage(_messages.messages.validationErrorDescriptionRequired);
106
+ }
107
+ return errors;
108
+ };
85
109
  var getFieldLabels = function getFieldLabels(record) {
86
110
  var _record$bug, _record$comment, _record$suggestion, _record$question, _record$empty, _record$not_relevant, _record$not_accurate, _record$too_slow, _record$unhelpful_lin, _record$other;
87
111
  return {
@@ -127,24 +151,42 @@ var FeedbackForm = function FeedbackForm(_ref) {
127
151
  shouldScrollInViewport: true
128
152
  }, /*#__PURE__*/_react.default.createElement(_form.default, {
129
153
  onSubmit: /*#__PURE__*/(0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee() {
154
+ var errors;
130
155
  return _regenerator.default.wrap(function _callee$(_context) {
131
156
  while (1) switch (_context.prev = _context.next) {
132
157
  case 0:
158
+ if (!useNewValidation) {
159
+ _context.next = 5;
160
+ break;
161
+ }
162
+ // New validation: validate on submit and show errors
163
+ errors = getValidationErrors(); // If there are validation errors, show them and don't submit
164
+ if (!(Object.keys(errors).length > 0)) {
165
+ _context.next = 5;
166
+ break;
167
+ }
168
+ setValidationErrors(errors);
169
+ return _context.abrupt("return");
170
+ case 5:
171
+ // Submit the form (both old and new validation paths reach here)
133
172
  setIsSubmitting(true);
134
- _context.next = 3;
173
+ _context.prev = 6;
174
+ _context.next = 9;
135
175
  return onSubmit({
136
176
  canBeContacted: canBeContacted,
137
177
  description: description,
138
178
  enrollInResearchGroup: enrollInResearchGroup,
139
179
  type: type
140
180
  });
141
- case 3:
181
+ case 9:
182
+ _context.prev = 9;
142
183
  setIsSubmitting(false);
143
- case 4:
184
+ return _context.finish(9);
185
+ case 12:
144
186
  case "end":
145
187
  return _context.stop();
146
188
  }
147
- }, _callee);
189
+ }, _callee, null, [[6,, 9, 12]]);
148
190
  }))
149
191
  }, function (_ref3) {
150
192
  var formProps = _ref3.formProps;
@@ -166,12 +208,20 @@ var FeedbackForm = function FeedbackForm(_ref) {
166
208
  var _ref4$fieldProps = _ref4.fieldProps,
167
209
  id = _ref4$fieldProps.id,
168
210
  restProps = (0, _objectWithoutProperties2.default)(_ref4$fieldProps, _excluded);
169
- return /*#__PURE__*/_react.default.createElement(_select.default, (0, _extends2.default)({}, restProps, {
211
+ return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_select.default, (0, _extends2.default)({}, restProps, {
170
212
  onChange: function onChange(option) {
171
213
  if (!option || option instanceof Array) {
172
214
  return;
173
215
  }
174
216
  setType(option.value);
217
+ // Clear validation error when user selects a type (only for new validation)
218
+ if (useNewValidation && validationErrors.type) {
219
+ setValidationErrors(function (prev) {
220
+ return _objectSpread(_objectSpread({}, prev), {}, {
221
+ type: undefined
222
+ });
223
+ });
224
+ }
175
225
  },
176
226
  menuPortalTarget: document.body,
177
227
  styles: {
@@ -187,22 +237,30 @@ var FeedbackForm = function FeedbackForm(_ref) {
187
237
  ref: focusRef,
188
238
  placeholder: getDefaultPlaceholder(feedbackGroupLabels),
189
239
  inputId: id
190
- }));
240
+ })), useNewValidation && validationErrors.type && /*#__PURE__*/_react.default.createElement(_form.ErrorMessage, null, validationErrors.type));
191
241
  }) : null, showDefaultTextFields && canShowTextField && /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_form.Field, {
192
242
  label: showTypeField ? getFieldLabels(feedbackGroupLabels)[type] : customTextAreaLabel || formatMessage(_messages.messages.defaultCustomTextAreaLabel),
193
243
  isRequired: true,
194
244
  name: "description"
195
245
  }, function (_ref5) {
196
246
  var fieldProps = _ref5.fieldProps;
197
- return /*#__PURE__*/_react.default.createElement(_textarea.default, (0, _extends2.default)({}, fieldProps, {
247
+ return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_textarea.default, (0, _extends2.default)({}, fieldProps, {
198
248
  name: "foo",
199
249
  minimumRows: 6,
200
250
  placeholder: summaryPlaceholder || undefined,
201
251
  onChange: function onChange(e) {
202
- return setDescription(e.target.value);
252
+ setDescription(e.target.value);
253
+ // Clear validation error when user types
254
+ if (useNewValidation && validationErrors.description) {
255
+ setValidationErrors(function (prev) {
256
+ return _objectSpread(_objectSpread({}, prev), {}, {
257
+ description: undefined
258
+ });
259
+ });
260
+ }
203
261
  },
204
262
  value: description
205
- }));
263
+ })), useNewValidation && validationErrors.description && /*#__PURE__*/_react.default.createElement(_form.ErrorMessage, null, validationErrors.description));
206
264
  }), !anonymousFeedback && /*#__PURE__*/_react.default.createElement(_form.Fieldset, null, /*#__PURE__*/_react.default.createElement("legend", {
207
265
  "aria-hidden": false,
208
266
  hidden: true
@@ -210,23 +268,24 @@ var FeedbackForm = function FeedbackForm(_ref) {
210
268
  name: "can-be-contacted"
211
269
  }, function (_ref6) {
212
270
  var fieldProps = _ref6.fieldProps;
213
- return /*#__PURE__*/_react.default.createElement(_checkbox.Checkbox, (0, _extends2.default)({}, fieldProps, {
271
+ return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_checkbox.Checkbox, (0, _extends2.default)({}, fieldProps, {
214
272
  "aria-describedby": undefined // JCA11Y-1988
215
273
  ,
216
- label: canBeContactedLabel || /*#__PURE__*/_react.default.createElement(_reactIntlNext.FormattedMessage, (0, _extends2.default)({}, _messages.messages.canBeContactedLabel, {
217
- values: {
218
- a: function a(chunks) {
219
- return /*#__PURE__*/_react.default.createElement(_link.default, {
220
- href: "https://www.atlassian.com/legal/privacy-policy",
221
- target: "_blank"
222
- }, chunks);
223
- }
224
- }
225
- })),
274
+ label: canBeContactedLabel || /*#__PURE__*/_react.default.createElement(_reactIntlNext.FormattedMessage, (0, _platformFeatureFlags.fg)('product-terminology-refresh') ? _messages.messages.canBeContactedLabelAppify : _messages.messages.canBeContactedLabel),
226
275
  onChange: function onChange(event) {
227
276
  return setCanBeContacted(event.target.checked);
228
277
  }
229
- }));
278
+ })), /*#__PURE__*/_react.default.createElement("span", {
279
+ style: {
280
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766
281
+ paddingInlineStart: "var(--ds-space-300, 24px)",
282
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766
283
+ marginInlineStart: "var(--ds-space-050, 4px)"
284
+ }
285
+ }, /*#__PURE__*/_react.default.createElement(_link.default, {
286
+ href: "https://www.atlassian.com/legal/privacy-policy",
287
+ target: "_blank"
288
+ }, formatMessage(_messages.messages.privacyPolicy))));
230
289
  }), /*#__PURE__*/_react.default.createElement(_form.Field, {
231
290
  name: "enroll-in-research-group"
232
291
  }, function (_ref7) {
@@ -18,9 +18,14 @@ var messages = exports.messages = (0, _reactIntlNext.defineMessages)({
18
18
  },
19
19
  canBeContactedLabel: {
20
20
  id: 'feedback-collector.can-be-contacted.label',
21
- defaultMessage: 'Yes, Atlassian teams can contact me to learn about my experiences to improve Atlassian products and services. I acknowledge the <a>Atlassian Privacy Policy</a>.',
21
+ defaultMessage: 'Yes, Atlassian teams can contact me to learn about my experiences to improve Atlassian products and services. I acknowledge the Atlassian Privacy Policy.',
22
22
  description: 'The checkbox label to give consent to be contacted about their feedback'
23
23
  },
24
+ privacyPolicy: {
25
+ id: 'feedback-collector.privacy-policy',
26
+ defaultMessage: 'Atlassian Privacy Policy',
27
+ description: 'The text content for the Privacy Policy link'
28
+ },
24
29
  summaryPlaceholder: {
25
30
  id: 'feedback-collector.summary-placeholder',
26
31
  defaultMessage: "Let us know what's on your mind",
@@ -155,5 +160,25 @@ var messages = exports.messages = (0, _reactIntlNext.defineMessages)({
155
160
  id: 'feedback-collector.default.custom.textarea.label',
156
161
  defaultMessage: "What's on your mind?",
157
162
  description: 'The textarea label where users can write their suggestion for custom feedback collector'
163
+ },
164
+ canBeContactedLabelAppify: {
165
+ id: 'feedback-collector.can-be-contacted.label-appify',
166
+ defaultMessage: 'Yes, Atlassian teams can contact me to learn about my experiences to improve Atlassian apps and services. I acknowledge the <a>Atlassian Privacy Policy</a>.',
167
+ description: 'The checkbox label to give consent to be contacted about their feedback'
168
+ },
169
+ feedbackSuccessFlagDescriptionAppify: {
170
+ id: 'feedback-collector.success-flag.description-appify',
171
+ defaultMessage: 'Your valuable feedback helps us continually improve our apps.',
172
+ description: 'Description shown when feedback is successfully submitted'
173
+ },
174
+ validationErrorTypeRequired: {
175
+ id: 'feedback-collector.validation.type.required',
176
+ defaultMessage: 'Please select a feedback type',
177
+ description: 'Error message when feedback type is not selected'
178
+ },
179
+ validationErrorDescriptionRequired: {
180
+ id: 'feedback-collector.validation.description.required',
181
+ defaultMessage: 'Please provide a description',
182
+ description: 'Error message when description is not provided'
158
183
  }
159
184
  });
@@ -91,7 +91,7 @@ export default class FeedbackCollector extends Component {
91
91
  return FeedbackCollector.defaultProps.url;
92
92
  }
93
93
  getPackageVersion() {
94
- return "14.3.2" || 'Unknown, at least 11.0.0';
94
+ return "14.3.4" || 'Unknown, at least 11.0.0';
95
95
  }
96
96
  async getEntitlementInformation() {
97
97
  var _entitlementDetails, _entitlementDetails2, _productName, _entitlement, _productEntitlement;
@@ -2,6 +2,7 @@ import React from 'react';
2
2
  import { useIntl } from 'react-intl-next';
3
3
  import { AutoDismissFlag } from '@atlaskit/flag';
4
4
  import SuccessIcon from '@atlaskit/icon/core/migration/status-success--check-circle';
5
+ import { fg } from '@atlaskit/platform-feature-flags';
5
6
  import { G300 } from '@atlaskit/theme/colors';
6
7
  import { messages } from '../messages';
7
8
  import { IntlProviderWithResolvedMessages } from './IntlProviderWithResolvedMessages';
@@ -19,7 +20,7 @@ const FeedbackFlag = ({
19
20
  label: "Success"
20
21
  }),
21
22
  id: "feedbackSent",
22
- description: description || formatMessage(messages.feedbackSuccessFlagDescription),
23
+ description: description || formatMessage(fg('product-terminology-refresh') ? messages.feedbackSuccessFlagDescriptionAppify : messages.feedbackSuccessFlagDescription),
23
24
  title: title || formatMessage(messages.feedbackSuccessFlagTitle)
24
25
  });
25
26
  };
@@ -3,9 +3,10 @@ import React, { useRef, useState } from 'react';
3
3
  import { FormattedMessage, useIntl } from 'react-intl-next';
4
4
  import Button from '@atlaskit/button/standard-button';
5
5
  import { Checkbox } from '@atlaskit/checkbox';
6
- import Form, { Field, Fieldset, RequiredAsterisk } from '@atlaskit/form';
6
+ import Form, { ErrorMessage, Field, Fieldset, RequiredAsterisk } from '@atlaskit/form';
7
7
  import Link from '@atlaskit/link';
8
8
  import Modal, { ModalBody, ModalFooter, ModalHeader, ModalTitle } from '@atlaskit/modal-dialog';
9
+ import { fg } from '@atlaskit/platform-feature-flags';
9
10
  import SectionMessage from '@atlaskit/section-message';
10
11
  import Select from '@atlaskit/select';
11
12
  import TextArea from '@atlaskit/textarea';
@@ -39,13 +40,33 @@ const FeedbackForm = ({
39
40
  const [enrollInResearchGroup, setEnrollInResearchGroup] = useState(false);
40
41
  const [type, setType] = useState('empty');
41
42
  const [isSubmitting, setIsSubmitting] = useState(false);
43
+ const [validationErrors, setValidationErrors] = useState({});
42
44
  const {
43
45
  formatMessage
44
46
  } = useIntl();
45
47
  const isTypeSelected = () => type !== 'empty';
46
48
  const canShowTextField = isTypeSelected() || !showTypeField;
47
49
  const hasDescription = description || hasDescriptionDefaultValue;
48
- const isDisabled = disableSubmitButton || (showTypeField ? !isTypeSelected() || !hasDescription : !hasDescription);
50
+
51
+ // Feature flag determines validation behavior
52
+ const useNewValidation = fg('feedback-collector-custom-validation');
53
+ const isDisabled = useNewValidation ? isSubmitting || disableSubmitButton // New: only disable when submitting or explicitly disabled
54
+ : disableSubmitButton || (showTypeField ? !isTypeSelected() || !hasDescription : !hasDescription); // Old: disable based on form validation
55
+
56
+ const getValidationErrors = () => {
57
+ const errors = {};
58
+
59
+ // Validate type selection if showTypeField is true
60
+ if (showTypeField && !isTypeSelected()) {
61
+ errors.type = formatMessage(messages.validationErrorTypeRequired);
62
+ }
63
+
64
+ // Validate description if showDefaultTextFields is true
65
+ if (showDefaultTextFields && !hasDescription) {
66
+ errors.description = formatMessage(messages.validationErrorDescriptionRequired);
67
+ }
68
+ return errors;
69
+ };
49
70
  const getFieldLabels = record => {
50
71
  var _record$bug, _record$comment, _record$suggestion, _record$question, _record$empty, _record$not_relevant, _record$not_accurate, _record$too_slow, _record$unhelpful_lin, _record$other;
51
72
  return {
@@ -91,14 +112,29 @@ const FeedbackForm = ({
91
112
  shouldScrollInViewport: true
92
113
  }, /*#__PURE__*/React.createElement(Form, {
93
114
  onSubmit: async () => {
115
+ if (useNewValidation) {
116
+ // New validation: validate on submit and show errors
117
+ const errors = getValidationErrors();
118
+
119
+ // If there are validation errors, show them and don't submit
120
+ if (Object.keys(errors).length > 0) {
121
+ setValidationErrors(errors);
122
+ return;
123
+ }
124
+ }
125
+
126
+ // Submit the form (both old and new validation paths reach here)
94
127
  setIsSubmitting(true);
95
- await onSubmit({
96
- canBeContacted,
97
- description,
98
- enrollInResearchGroup,
99
- type
100
- });
101
- setIsSubmitting(false);
128
+ try {
129
+ await onSubmit({
130
+ canBeContacted,
131
+ description,
132
+ enrollInResearchGroup,
133
+ type
134
+ });
135
+ } finally {
136
+ setIsSubmitting(false);
137
+ }
102
138
  }
103
139
  }, ({
104
140
  formProps
@@ -121,12 +157,19 @@ const FeedbackForm = ({
121
157
  id,
122
158
  ...restProps
123
159
  }
124
- }) => /*#__PURE__*/React.createElement(Select, _extends({}, restProps, {
160
+ }) => /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Select, _extends({}, restProps, {
125
161
  onChange: option => {
126
162
  if (!option || option instanceof Array) {
127
163
  return;
128
164
  }
129
165
  setType(option.value);
166
+ // Clear validation error when user selects a type (only for new validation)
167
+ if (useNewValidation && validationErrors.type) {
168
+ setValidationErrors(prev => ({
169
+ ...prev,
170
+ type: undefined
171
+ }));
172
+ }
130
173
  },
131
174
  menuPortalTarget: document.body,
132
175
  styles: {
@@ -141,38 +184,50 @@ const FeedbackForm = ({
141
184
  ref: focusRef,
142
185
  placeholder: getDefaultPlaceholder(feedbackGroupLabels),
143
186
  inputId: id
144
- }))) : null, showDefaultTextFields && canShowTextField && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Field, {
187
+ })), useNewValidation && validationErrors.type && /*#__PURE__*/React.createElement(ErrorMessage, null, validationErrors.type))) : null, showDefaultTextFields && canShowTextField && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Field, {
145
188
  label: showTypeField ? getFieldLabels(feedbackGroupLabels)[type] : customTextAreaLabel || formatMessage(messages.defaultCustomTextAreaLabel),
146
189
  isRequired: true,
147
190
  name: "description"
148
191
  }, ({
149
192
  fieldProps
150
- }) => /*#__PURE__*/React.createElement(TextArea, _extends({}, fieldProps, {
193
+ }) => /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(TextArea, _extends({}, fieldProps, {
151
194
  name: "foo",
152
195
  minimumRows: 6,
153
196
  placeholder: summaryPlaceholder || undefined,
154
- onChange: e => setDescription(e.target.value),
197
+ onChange: e => {
198
+ setDescription(e.target.value);
199
+ // Clear validation error when user types
200
+ if (useNewValidation && validationErrors.description) {
201
+ setValidationErrors(prev => ({
202
+ ...prev,
203
+ description: undefined
204
+ }));
205
+ }
206
+ },
155
207
  value: description
156
- }))), !anonymousFeedback && /*#__PURE__*/React.createElement(Fieldset, null, /*#__PURE__*/React.createElement("legend", {
208
+ })), useNewValidation && validationErrors.description && /*#__PURE__*/React.createElement(ErrorMessage, null, validationErrors.description))), !anonymousFeedback && /*#__PURE__*/React.createElement(Fieldset, null, /*#__PURE__*/React.createElement("legend", {
157
209
  "aria-hidden": false,
158
210
  hidden: true
159
211
  }, "Atlassian opt-in options"), /*#__PURE__*/React.createElement(Field, {
160
212
  name: "can-be-contacted"
161
213
  }, ({
162
214
  fieldProps
163
- }) => /*#__PURE__*/React.createElement(Checkbox, _extends({}, fieldProps, {
215
+ }) => /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Checkbox, _extends({}, fieldProps, {
164
216
  "aria-describedby": undefined // JCA11Y-1988
165
217
  ,
166
- label: canBeContactedLabel || /*#__PURE__*/React.createElement(FormattedMessage, _extends({}, messages.canBeContactedLabel, {
167
- values: {
168
- a: chunks => /*#__PURE__*/React.createElement(Link, {
169
- href: "https://www.atlassian.com/legal/privacy-policy",
170
- target: "_blank"
171
- }, chunks)
172
- }
173
- })),
218
+ label: canBeContactedLabel || /*#__PURE__*/React.createElement(FormattedMessage, fg('product-terminology-refresh') ? messages.canBeContactedLabelAppify : messages.canBeContactedLabel),
174
219
  onChange: event => setCanBeContacted(event.target.checked)
175
- }))), /*#__PURE__*/React.createElement(Field, {
220
+ })), /*#__PURE__*/React.createElement("span", {
221
+ style: {
222
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766
223
+ paddingInlineStart: "var(--ds-space-300, 24px)",
224
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766
225
+ marginInlineStart: "var(--ds-space-050, 4px)"
226
+ }
227
+ }, /*#__PURE__*/React.createElement(Link, {
228
+ href: "https://www.atlassian.com/legal/privacy-policy",
229
+ target: "_blank"
230
+ }, formatMessage(messages.privacyPolicy))))), /*#__PURE__*/React.createElement(Field, {
176
231
  name: "enroll-in-research-group"
177
232
  }, ({
178
233
  fieldProps
@@ -12,9 +12,14 @@ export const messages = defineMessages({
12
12
  },
13
13
  canBeContactedLabel: {
14
14
  id: 'feedback-collector.can-be-contacted.label',
15
- defaultMessage: 'Yes, Atlassian teams can contact me to learn about my experiences to improve Atlassian products and services. I acknowledge the <a>Atlassian Privacy Policy</a>.',
15
+ defaultMessage: 'Yes, Atlassian teams can contact me to learn about my experiences to improve Atlassian products and services. I acknowledge the Atlassian Privacy Policy.',
16
16
  description: 'The checkbox label to give consent to be contacted about their feedback'
17
17
  },
18
+ privacyPolicy: {
19
+ id: 'feedback-collector.privacy-policy',
20
+ defaultMessage: 'Atlassian Privacy Policy',
21
+ description: 'The text content for the Privacy Policy link'
22
+ },
18
23
  summaryPlaceholder: {
19
24
  id: 'feedback-collector.summary-placeholder',
20
25
  defaultMessage: "Let us know what's on your mind",
@@ -149,5 +154,25 @@ export const messages = defineMessages({
149
154
  id: 'feedback-collector.default.custom.textarea.label',
150
155
  defaultMessage: "What's on your mind?",
151
156
  description: 'The textarea label where users can write their suggestion for custom feedback collector'
157
+ },
158
+ canBeContactedLabelAppify: {
159
+ id: 'feedback-collector.can-be-contacted.label-appify',
160
+ defaultMessage: 'Yes, Atlassian teams can contact me to learn about my experiences to improve Atlassian apps and services. I acknowledge the <a>Atlassian Privacy Policy</a>.',
161
+ description: 'The checkbox label to give consent to be contacted about their feedback'
162
+ },
163
+ feedbackSuccessFlagDescriptionAppify: {
164
+ id: 'feedback-collector.success-flag.description-appify',
165
+ defaultMessage: 'Your valuable feedback helps us continually improve our apps.',
166
+ description: 'Description shown when feedback is successfully submitted'
167
+ },
168
+ validationErrorTypeRequired: {
169
+ id: 'feedback-collector.validation.type.required',
170
+ defaultMessage: 'Please select a feedback type',
171
+ description: 'Error message when feedback type is not selected'
172
+ },
173
+ validationErrorDescriptionRequired: {
174
+ id: 'feedback-collector.validation.description.required',
175
+ defaultMessage: 'Please provide a description',
176
+ description: 'Error message when description is not provided'
152
177
  }
153
178
  });
@@ -154,7 +154,7 @@ var FeedbackCollector = /*#__PURE__*/function (_Component) {
154
154
  }, {
155
155
  key: "getPackageVersion",
156
156
  value: function getPackageVersion() {
157
- return "14.3.2" || 'Unknown, at least 11.0.0';
157
+ return "14.3.4" || 'Unknown, at least 11.0.0';
158
158
  }
159
159
  }, {
160
160
  key: "getEntitlementInformation",
@@ -2,6 +2,7 @@ import React from 'react';
2
2
  import { useIntl } from 'react-intl-next';
3
3
  import { AutoDismissFlag } from '@atlaskit/flag';
4
4
  import SuccessIcon from '@atlaskit/icon/core/migration/status-success--check-circle';
5
+ import { fg } from '@atlaskit/platform-feature-flags';
5
6
  import { G300 } from '@atlaskit/theme/colors';
6
7
  import { messages } from '../messages';
7
8
  import { IntlProviderWithResolvedMessages } from './IntlProviderWithResolvedMessages';
@@ -17,7 +18,7 @@ var FeedbackFlag = function FeedbackFlag(_ref) {
17
18
  label: "Success"
18
19
  }),
19
20
  id: "feedbackSent",
20
- description: description || formatMessage(messages.feedbackSuccessFlagDescription),
21
+ description: description || formatMessage(fg('product-terminology-refresh') ? messages.feedbackSuccessFlagDescriptionAppify : messages.feedbackSuccessFlagDescription),
21
22
  title: title || formatMessage(messages.feedbackSuccessFlagTitle)
22
23
  });
23
24
  };
@@ -12,9 +12,10 @@ import React, { useRef, useState } from 'react';
12
12
  import { FormattedMessage, useIntl } from 'react-intl-next';
13
13
  import Button from '@atlaskit/button/standard-button';
14
14
  import { Checkbox } from '@atlaskit/checkbox';
15
- import Form, { Field, Fieldset, RequiredAsterisk } from '@atlaskit/form';
15
+ import Form, { ErrorMessage, Field, Fieldset, RequiredAsterisk } from '@atlaskit/form';
16
16
  import Link from '@atlaskit/link';
17
17
  import Modal, { ModalBody, ModalFooter, ModalHeader, ModalTitle } from '@atlaskit/modal-dialog';
18
+ import { fg } from '@atlaskit/platform-feature-flags';
18
19
  import SectionMessage from '@atlaskit/section-message';
19
20
  import Select from '@atlaskit/select';
20
21
  import TextArea from '@atlaskit/textarea';
@@ -65,6 +66,10 @@ var FeedbackForm = function FeedbackForm(_ref) {
65
66
  _useState0 = _slicedToArray(_useState9, 2),
66
67
  isSubmitting = _useState0[0],
67
68
  setIsSubmitting = _useState0[1];
69
+ var _useState1 = useState({}),
70
+ _useState10 = _slicedToArray(_useState1, 2),
71
+ validationErrors = _useState10[0],
72
+ setValidationErrors = _useState10[1];
68
73
  var _useIntl = useIntl(),
69
74
  formatMessage = _useIntl.formatMessage;
70
75
  var isTypeSelected = function isTypeSelected() {
@@ -72,7 +77,26 @@ var FeedbackForm = function FeedbackForm(_ref) {
72
77
  };
73
78
  var canShowTextField = isTypeSelected() || !showTypeField;
74
79
  var hasDescription = description || hasDescriptionDefaultValue;
75
- var isDisabled = disableSubmitButton || (showTypeField ? !isTypeSelected() || !hasDescription : !hasDescription);
80
+
81
+ // Feature flag determines validation behavior
82
+ var useNewValidation = fg('feedback-collector-custom-validation');
83
+ var isDisabled = useNewValidation ? isSubmitting || disableSubmitButton // New: only disable when submitting or explicitly disabled
84
+ : disableSubmitButton || (showTypeField ? !isTypeSelected() || !hasDescription : !hasDescription); // Old: disable based on form validation
85
+
86
+ var getValidationErrors = function getValidationErrors() {
87
+ var errors = {};
88
+
89
+ // Validate type selection if showTypeField is true
90
+ if (showTypeField && !isTypeSelected()) {
91
+ errors.type = formatMessage(messages.validationErrorTypeRequired);
92
+ }
93
+
94
+ // Validate description if showDefaultTextFields is true
95
+ if (showDefaultTextFields && !hasDescription) {
96
+ errors.description = formatMessage(messages.validationErrorDescriptionRequired);
97
+ }
98
+ return errors;
99
+ };
76
100
  var getFieldLabels = function getFieldLabels(record) {
77
101
  var _record$bug, _record$comment, _record$suggestion, _record$question, _record$empty, _record$not_relevant, _record$not_accurate, _record$too_slow, _record$unhelpful_lin, _record$other;
78
102
  return {
@@ -118,24 +142,42 @@ var FeedbackForm = function FeedbackForm(_ref) {
118
142
  shouldScrollInViewport: true
119
143
  }, /*#__PURE__*/React.createElement(Form, {
120
144
  onSubmit: /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee() {
145
+ var errors;
121
146
  return _regeneratorRuntime.wrap(function _callee$(_context) {
122
147
  while (1) switch (_context.prev = _context.next) {
123
148
  case 0:
149
+ if (!useNewValidation) {
150
+ _context.next = 5;
151
+ break;
152
+ }
153
+ // New validation: validate on submit and show errors
154
+ errors = getValidationErrors(); // If there are validation errors, show them and don't submit
155
+ if (!(Object.keys(errors).length > 0)) {
156
+ _context.next = 5;
157
+ break;
158
+ }
159
+ setValidationErrors(errors);
160
+ return _context.abrupt("return");
161
+ case 5:
162
+ // Submit the form (both old and new validation paths reach here)
124
163
  setIsSubmitting(true);
125
- _context.next = 3;
164
+ _context.prev = 6;
165
+ _context.next = 9;
126
166
  return onSubmit({
127
167
  canBeContacted: canBeContacted,
128
168
  description: description,
129
169
  enrollInResearchGroup: enrollInResearchGroup,
130
170
  type: type
131
171
  });
132
- case 3:
172
+ case 9:
173
+ _context.prev = 9;
133
174
  setIsSubmitting(false);
134
- case 4:
175
+ return _context.finish(9);
176
+ case 12:
135
177
  case "end":
136
178
  return _context.stop();
137
179
  }
138
- }, _callee);
180
+ }, _callee, null, [[6,, 9, 12]]);
139
181
  }))
140
182
  }, function (_ref3) {
141
183
  var formProps = _ref3.formProps;
@@ -157,12 +199,20 @@ var FeedbackForm = function FeedbackForm(_ref) {
157
199
  var _ref4$fieldProps = _ref4.fieldProps,
158
200
  id = _ref4$fieldProps.id,
159
201
  restProps = _objectWithoutProperties(_ref4$fieldProps, _excluded);
160
- return /*#__PURE__*/React.createElement(Select, _extends({}, restProps, {
202
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Select, _extends({}, restProps, {
161
203
  onChange: function onChange(option) {
162
204
  if (!option || option instanceof Array) {
163
205
  return;
164
206
  }
165
207
  setType(option.value);
208
+ // Clear validation error when user selects a type (only for new validation)
209
+ if (useNewValidation && validationErrors.type) {
210
+ setValidationErrors(function (prev) {
211
+ return _objectSpread(_objectSpread({}, prev), {}, {
212
+ type: undefined
213
+ });
214
+ });
215
+ }
166
216
  },
167
217
  menuPortalTarget: document.body,
168
218
  styles: {
@@ -178,22 +228,30 @@ var FeedbackForm = function FeedbackForm(_ref) {
178
228
  ref: focusRef,
179
229
  placeholder: getDefaultPlaceholder(feedbackGroupLabels),
180
230
  inputId: id
181
- }));
231
+ })), useNewValidation && validationErrors.type && /*#__PURE__*/React.createElement(ErrorMessage, null, validationErrors.type));
182
232
  }) : null, showDefaultTextFields && canShowTextField && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Field, {
183
233
  label: showTypeField ? getFieldLabels(feedbackGroupLabels)[type] : customTextAreaLabel || formatMessage(messages.defaultCustomTextAreaLabel),
184
234
  isRequired: true,
185
235
  name: "description"
186
236
  }, function (_ref5) {
187
237
  var fieldProps = _ref5.fieldProps;
188
- return /*#__PURE__*/React.createElement(TextArea, _extends({}, fieldProps, {
238
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(TextArea, _extends({}, fieldProps, {
189
239
  name: "foo",
190
240
  minimumRows: 6,
191
241
  placeholder: summaryPlaceholder || undefined,
192
242
  onChange: function onChange(e) {
193
- return setDescription(e.target.value);
243
+ setDescription(e.target.value);
244
+ // Clear validation error when user types
245
+ if (useNewValidation && validationErrors.description) {
246
+ setValidationErrors(function (prev) {
247
+ return _objectSpread(_objectSpread({}, prev), {}, {
248
+ description: undefined
249
+ });
250
+ });
251
+ }
194
252
  },
195
253
  value: description
196
- }));
254
+ })), useNewValidation && validationErrors.description && /*#__PURE__*/React.createElement(ErrorMessage, null, validationErrors.description));
197
255
  }), !anonymousFeedback && /*#__PURE__*/React.createElement(Fieldset, null, /*#__PURE__*/React.createElement("legend", {
198
256
  "aria-hidden": false,
199
257
  hidden: true
@@ -201,23 +259,24 @@ var FeedbackForm = function FeedbackForm(_ref) {
201
259
  name: "can-be-contacted"
202
260
  }, function (_ref6) {
203
261
  var fieldProps = _ref6.fieldProps;
204
- return /*#__PURE__*/React.createElement(Checkbox, _extends({}, fieldProps, {
262
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Checkbox, _extends({}, fieldProps, {
205
263
  "aria-describedby": undefined // JCA11Y-1988
206
264
  ,
207
- label: canBeContactedLabel || /*#__PURE__*/React.createElement(FormattedMessage, _extends({}, messages.canBeContactedLabel, {
208
- values: {
209
- a: function a(chunks) {
210
- return /*#__PURE__*/React.createElement(Link, {
211
- href: "https://www.atlassian.com/legal/privacy-policy",
212
- target: "_blank"
213
- }, chunks);
214
- }
215
- }
216
- })),
265
+ label: canBeContactedLabel || /*#__PURE__*/React.createElement(FormattedMessage, fg('product-terminology-refresh') ? messages.canBeContactedLabelAppify : messages.canBeContactedLabel),
217
266
  onChange: function onChange(event) {
218
267
  return setCanBeContacted(event.target.checked);
219
268
  }
220
- }));
269
+ })), /*#__PURE__*/React.createElement("span", {
270
+ style: {
271
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766
272
+ paddingInlineStart: "var(--ds-space-300, 24px)",
273
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766
274
+ marginInlineStart: "var(--ds-space-050, 4px)"
275
+ }
276
+ }, /*#__PURE__*/React.createElement(Link, {
277
+ href: "https://www.atlassian.com/legal/privacy-policy",
278
+ target: "_blank"
279
+ }, formatMessage(messages.privacyPolicy))));
221
280
  }), /*#__PURE__*/React.createElement(Field, {
222
281
  name: "enroll-in-research-group"
223
282
  }, function (_ref7) {
@@ -12,9 +12,14 @@ export var messages = defineMessages({
12
12
  },
13
13
  canBeContactedLabel: {
14
14
  id: 'feedback-collector.can-be-contacted.label',
15
- defaultMessage: 'Yes, Atlassian teams can contact me to learn about my experiences to improve Atlassian products and services. I acknowledge the <a>Atlassian Privacy Policy</a>.',
15
+ defaultMessage: 'Yes, Atlassian teams can contact me to learn about my experiences to improve Atlassian products and services. I acknowledge the Atlassian Privacy Policy.',
16
16
  description: 'The checkbox label to give consent to be contacted about their feedback'
17
17
  },
18
+ privacyPolicy: {
19
+ id: 'feedback-collector.privacy-policy',
20
+ defaultMessage: 'Atlassian Privacy Policy',
21
+ description: 'The text content for the Privacy Policy link'
22
+ },
18
23
  summaryPlaceholder: {
19
24
  id: 'feedback-collector.summary-placeholder',
20
25
  defaultMessage: "Let us know what's on your mind",
@@ -149,5 +154,25 @@ export var messages = defineMessages({
149
154
  id: 'feedback-collector.default.custom.textarea.label',
150
155
  defaultMessage: "What's on your mind?",
151
156
  description: 'The textarea label where users can write their suggestion for custom feedback collector'
157
+ },
158
+ canBeContactedLabelAppify: {
159
+ id: 'feedback-collector.can-be-contacted.label-appify',
160
+ defaultMessage: 'Yes, Atlassian teams can contact me to learn about my experiences to improve Atlassian apps and services. I acknowledge the <a>Atlassian Privacy Policy</a>.',
161
+ description: 'The checkbox label to give consent to be contacted about their feedback'
162
+ },
163
+ feedbackSuccessFlagDescriptionAppify: {
164
+ id: 'feedback-collector.success-flag.description-appify',
165
+ defaultMessage: 'Your valuable feedback helps us continually improve our apps.',
166
+ description: 'Description shown when feedback is successfully submitted'
167
+ },
168
+ validationErrorTypeRequired: {
169
+ id: 'feedback-collector.validation.type.required',
170
+ defaultMessage: 'Please select a feedback type',
171
+ description: 'Error message when feedback type is not selected'
172
+ },
173
+ validationErrorDescriptionRequired: {
174
+ id: 'feedback-collector.validation.description.required',
175
+ defaultMessage: 'Please provide a description',
176
+ description: 'Error message when description is not provided'
152
177
  }
153
178
  });
@@ -14,6 +14,11 @@ export declare const messages: {
14
14
  defaultMessage: string;
15
15
  description: string;
16
16
  };
17
+ privacyPolicy: {
18
+ id: string;
19
+ defaultMessage: string;
20
+ description: string;
21
+ };
17
22
  summaryPlaceholder: {
18
23
  id: string;
19
24
  defaultMessage: string;
@@ -149,4 +154,24 @@ export declare const messages: {
149
154
  defaultMessage: string;
150
155
  description: string;
151
156
  };
157
+ canBeContactedLabelAppify: {
158
+ id: string;
159
+ defaultMessage: string;
160
+ description: string;
161
+ };
162
+ feedbackSuccessFlagDescriptionAppify: {
163
+ id: string;
164
+ defaultMessage: string;
165
+ description: string;
166
+ };
167
+ validationErrorTypeRequired: {
168
+ id: string;
169
+ defaultMessage: string;
170
+ description: string;
171
+ };
172
+ validationErrorDescriptionRequired: {
173
+ id: string;
174
+ defaultMessage: string;
175
+ description: string;
176
+ };
152
177
  };
@@ -14,6 +14,11 @@ export declare const messages: {
14
14
  defaultMessage: string;
15
15
  description: string;
16
16
  };
17
+ privacyPolicy: {
18
+ id: string;
19
+ defaultMessage: string;
20
+ description: string;
21
+ };
17
22
  summaryPlaceholder: {
18
23
  id: string;
19
24
  defaultMessage: string;
@@ -149,4 +154,24 @@ export declare const messages: {
149
154
  defaultMessage: string;
150
155
  description: string;
151
156
  };
157
+ canBeContactedLabelAppify: {
158
+ id: string;
159
+ defaultMessage: string;
160
+ description: string;
161
+ };
162
+ feedbackSuccessFlagDescriptionAppify: {
163
+ id: string;
164
+ defaultMessage: string;
165
+ description: string;
166
+ };
167
+ validationErrorTypeRequired: {
168
+ id: string;
169
+ defaultMessage: string;
170
+ description: string;
171
+ };
172
+ validationErrorDescriptionRequired: {
173
+ id: string;
174
+ defaultMessage: string;
175
+ description: string;
176
+ };
152
177
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/feedback-collector",
3
- "version": "14.3.3",
3
+ "version": "14.4.0",
4
4
  "description": "A component that collects feedback across Atlassian products.",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org/"
@@ -40,8 +40,8 @@
40
40
  "@atlaskit/button": "^23.4.0",
41
41
  "@atlaskit/checkbox": "^17.1.0",
42
42
  "@atlaskit/flag": "^17.3.0",
43
- "@atlaskit/form": "^12.1.0",
44
- "@atlaskit/icon": "^28.0.0",
43
+ "@atlaskit/form": "^12.2.0",
44
+ "@atlaskit/icon": "^28.1.0",
45
45
  "@atlaskit/link": "^3.2.0",
46
46
  "@atlaskit/modal-dialog": "^14.3.0",
47
47
  "@atlaskit/platform-feature-flags": "^1.1.0",
@@ -90,6 +90,12 @@
90
90
  },
91
91
  "dst-a11y__replace-anchor-with-link__belugas-feedba": {
92
92
  "type": "boolean"
93
+ },
94
+ "product-terminology-refresh": {
95
+ "type": "boolean"
96
+ },
97
+ "feedback-collector-custom-validation": {
98
+ "type": "boolean"
93
99
  }
94
100
  }
95
101
  }