asab_webui_shell 27.3.11 → 27.3.13

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.
@@ -74,7 +74,10 @@ function AuthHeaderDropdown(props) {
74
74
  divider: true
75
75
  }), /*#__PURE__*/_react.default.createElement(_reactstrap.DropdownItem, {
76
76
  tag: _reactRouter.Link,
77
- to: "/auth/invite"
77
+ to: "/auth/invite",
78
+ state: {
79
+ clearInvitation: true
80
+ }
78
81
  }, t('AuthHeader|Invite other users')), /*#__PURE__*/_react.default.createElement(_reactstrap.DropdownItem, {
79
82
  divider: true
80
83
  })), /*#__PURE__*/_react.default.createElement(_reactstrap.DropdownItem, {
@@ -8,16 +8,17 @@ exports.default = InvitationScreen;
8
8
  var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
9
9
  var _react = _interopRequireWildcard(require("react"));
10
10
  var _reactI18next = require("react-i18next");
11
- var _asab_webui_components = require("asab_webui_components");
12
11
  var _reactRouter = require("react-router");
13
12
  var _reactstrap = require("reactstrap");
13
+ var _asab_webui_components = require("asab_webui_components");
14
+ var _seacatAuth = require("asab_webui_components/seacat-auth");
14
15
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
15
16
  // Component that handles user invitation
16
17
  function InvitationScreen(props) {
18
+ var _location$state2;
17
19
  var [emailValue, setEmailValue] = (0, _react.useState)('');
18
- var [isInvitationSuccessful, setIsInvitationSuccessful] = (0, _react.useState)(undefined);
19
- var [registrationUrl, setRegistrationUrl] = (0, _react.useState)(undefined);
20
- var [urlCopied, setUrlCopied] = (0, _react.useState)(undefined);
20
+ var [responseData, setResponseData] = (0, _react.useState)(undefined);
21
+ var [isSubmitting, setIsSubmitting] = (0, _react.useState)(false);
21
22
  var tenant = (0, _asab_webui_components.useAppSelector)(state => {
22
23
  var _state$tenant;
23
24
  return (_state$tenant = state.tenant) === null || _state$tenant === void 0 ? void 0 : _state$tenant.current;
@@ -27,27 +28,24 @@ function InvitationScreen(props) {
27
28
  } = (0, _reactI18next.useTranslation)();
28
29
  var navigate = (0, _reactRouter.useNavigate)();
29
30
  var SeaCatAuthAPI = props.app.axiosCreate('seacat-auth');
30
-
31
- // Copy registration URL if there is any
32
- var copyRegistrationUrl = () => {
33
- if (!registrationUrl) {
34
- console.error('No registration URL to copy.');
31
+ var canAccessCredentialsDetail = (0, _seacatAuth.isAuthorized)(['seacat:credentials:access'], props.app);
32
+ var location = (0, _reactRouter.useLocation)();
33
+ (0, _react.useEffect)(() => {
34
+ var _location$state;
35
+ if ((location === null || location === void 0 || (_location$state = location.state) === null || _location$state === void 0 ? void 0 : _location$state.clearInvitation) !== true) {
35
36
  return;
36
37
  }
37
- navigator.clipboard.writeText(registrationUrl).then(() => {
38
- setUrlCopied(true);
39
- var timeoutId = setTimeout(() => setUrlCopied(false), 3000);
40
- return () => {
41
- clearTimeout(timeoutId);
42
- };
43
- }).catch(error => {
44
- console.error('Failed to copy registration URL: ', error);
38
+ // Clear the invitation data and reload the page without the parameter to ensure showing an empty form again
39
+ setResponseData(undefined);
40
+ setEmailValue('');
41
+ navigate(location.pathname, {
42
+ replace: true,
43
+ state: {}
45
44
  });
46
- };
45
+ }, [location === null || location === void 0 || (_location$state2 = location.state) === null || _location$state2 === void 0 ? void 0 : _location$state2.clearInvitation]);
47
46
 
48
- // Validate email input
49
- var emailValidation = e => {
50
- e.preventDefault();
47
+ // Handle email input change
48
+ var handleEmailChange = e => {
51
49
  setEmailValue(e.target.value);
52
50
  };
53
51
 
@@ -55,94 +53,78 @@ function InvitationScreen(props) {
55
53
  var sendInvitation = /*#__PURE__*/function () {
56
54
  var _ref = (0, _asyncToGenerator2.default)(function* (e) {
57
55
  e.preventDefault();
56
+ if (isSubmitting) return;
57
+ setIsSubmitting(true);
58
58
  var body = {
59
59
  email: emailValue
60
60
  };
61
61
  try {
62
- var _response$data, _response$data2;
63
62
  var response = yield SeaCatAuthAPI.post("/account/".concat(tenant, "/invite"), body);
64
- if ((response === null || response === void 0 || (_response$data = response.data) === null || _response$data === void 0 ? void 0 : _response$data.result) !== 'OK') {
65
- throw new Error('Invitation has not been sent');
66
- }
67
- setIsInvitationSuccessful(true);
63
+ setResponseData(response === null || response === void 0 ? void 0 : response.data);
68
64
  setEmailValue('');
69
- if (response !== null && response !== void 0 && (_response$data2 = response.data) !== null && _response$data2 !== void 0 && _response$data2.registration_url) {
70
- var _response$data3;
71
- setRegistrationUrl(response === null || response === void 0 || (_response$data3 = response.data) === null || _response$data3 === void 0 ? void 0 : _response$data3.registration_url);
72
- } else {
73
- setRegistrationUrl(undefined);
74
- }
75
65
  } catch (e) {
76
66
  var _e$response;
77
- setIsInvitationSuccessful(false);
78
- if (e !== null && e !== void 0 && (_e$response = e.response) !== null && _e$response !== void 0 && (_e$response = _e$response.data) !== null && _e$response !== void 0 && _e$response.registration_url) {
67
+ if (e !== null && e !== void 0 && (_e$response = e.response) !== null && _e$response !== void 0 && _e$response.data) {
79
68
  var _e$response2;
80
- setRegistrationUrl(e === null || e === void 0 || (_e$response2 = e.response) === null || _e$response2 === void 0 || (_e$response2 = _e$response2.data) === null || _e$response2 === void 0 ? void 0 : _e$response2.registration_url);
81
- } else {
82
- setRegistrationUrl(undefined);
69
+ setResponseData(e === null || e === void 0 || (_e$response2 = e.response) === null || _e$response2 === void 0 ? void 0 : _e$response2.data);
70
+ return;
83
71
  }
72
+ props.app.addAlertFromException(e, t('InvitationScreen|Failed to create invitation'));
73
+ } finally {
74
+ setIsSubmitting(false);
84
75
  }
85
76
  });
86
77
  return function sendInvitation(_x) {
87
78
  return _ref.apply(this, arguments);
88
79
  };
89
80
  }();
90
-
91
- // Component that displays the registration URL with a copy button
92
- var CopyableRegistrationUrl = _ref2 => {
93
- var {
94
- registrationUrl
95
- } = _ref2;
96
- return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_reactstrap.FormText, null, t('InvitationScreen|If you want to invite the user manually, message them the registration URL below:')), /*#__PURE__*/_react.default.createElement(_reactstrap.InputGroup, {
97
- className: "mt-2 mb-3"
98
- }, /*#__PURE__*/_react.default.createElement(_reactstrap.Input, {
99
- readOnly: true,
81
+ if (responseData) {
82
+ var _responseData$email_s, _responseData$email_s2;
83
+ if (responseData.result !== 'OK') {
84
+ var responseError = responseData.error;
85
+ return /*#__PURE__*/_react.default.createElement(_asab_webui_components.ResultCard, {
86
+ status: "danger"
87
+ }, /*#__PURE__*/_react.default.createElement("h5", null, t('InvitationScreen|Invitation request failed')), responseError && /*#__PURE__*/_react.default.createElement("p", null, t(responseError)), /*#__PURE__*/_react.default.createElement("div", {
88
+ className: "mt-2"
89
+ }, /*#__PURE__*/_react.default.createElement(_reactstrap.Button, {
90
+ onClick: () => setResponseData(undefined),
91
+ color: "primary",
92
+ size: "lg"
93
+ }, t('General|Back'))));
94
+ }
95
+ var emailSent = ((_responseData$email_s = responseData.email_sent) === null || _responseData$email_s === void 0 ? void 0 : _responseData$email_s.result) === 'OK';
96
+ var emailError = ((_responseData$email_s2 = responseData.email_sent) === null || _responseData$email_s2 === void 0 ? void 0 : _responseData$email_s2.error) || 'InvitationScreen|Unknown error';
97
+ var registrationUrl = responseData.registration_url;
98
+ var credentialsId = responseData.credentials_id;
99
+ return /*#__PURE__*/_react.default.createElement(_asab_webui_components.ResultCard, {
100
+ status: emailSent ? 'success' : 'warning'
101
+ }, /*#__PURE__*/_react.default.createElement("h5", {
102
+ className: "mb-3"
103
+ }, t('InvitationScreen|Invitation was created successfully')), emailSent ? /*#__PURE__*/_react.default.createElement("p", null, t('InvitationScreen|The user will receive an email with registration link.')) : /*#__PURE__*/_react.default.createElement("p", null, t('InvitationScreen|However, it was not possible to send the invitation to the user via email ({{reason}}).', {
104
+ reason: t(emailError)
105
+ })), registrationUrl && /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, emailSent ? /*#__PURE__*/_react.default.createElement("p", null, t('InvitationScreen|You can also share the link manually:')) : /*#__PURE__*/_react.default.createElement("p", null, t('InvitationScreen|You can share the link manually:')), /*#__PURE__*/_react.default.createElement("div", null, /*#__PURE__*/_react.default.createElement(_asab_webui_components.CopyableInput, {
106
+ className: "mt-2 mb-3",
100
107
  value: registrationUrl
101
- }), /*#__PURE__*/_react.default.createElement(_reactstrap.Button, {
102
- outline: true,
108
+ }))), canAccessCredentialsDetail && credentialsId && /*#__PURE__*/_react.default.createElement("div", {
109
+ className: "mt-2 mb-3"
110
+ }, /*#__PURE__*/_react.default.createElement(_reactRouter.Link, {
111
+ to: "/auth/credentials/".concat(credentialsId)
112
+ }, t('InvitationScreen|Go to credentials detail'))), /*#__PURE__*/_react.default.createElement("div", {
113
+ className: "mt-2"
114
+ }, /*#__PURE__*/_react.default.createElement(_reactstrap.Button, {
115
+ onClick: () => navigate(-1),
103
116
  color: "primary",
104
- className: "text-nowrap",
105
- onClick: copyRegistrationUrl
106
- }, /*#__PURE__*/_react.default.createElement("i", {
107
- className: urlCopied ? 'bi bi-clipboard-check pe-2' : 'bi bi-clipboard pe-2',
108
- title: t('InvitationScreen|Copy to clipboard')
109
- }), urlCopied ? t('InvitationScreen|Copied!') : t('InvitationScreen|Copy URL'))));
110
- };
111
-
112
- // Display for successful invitation
113
- var SuccessfulInvitationCardBody = () => /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement("h6", null, t('InvitationScreen|Invitation was sent successfully')), registrationUrl && /*#__PURE__*/_react.default.createElement(CopyableRegistrationUrl, {
114
- registrationUrl: registrationUrl
115
- }), /*#__PURE__*/_react.default.createElement("div", {
116
- className: "mt-2"
117
- }, /*#__PURE__*/_react.default.createElement(_reactstrap.Button, {
118
- onClick: () => navigate('/auth/credentials'),
119
- color: "primary",
120
- size: "lg"
121
- }, t('General|Continue'))));
122
-
123
- // Display for unsuccessful invitation
124
- var UnsuccessfulInvitationCardBody = () => /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement("h6", null, t('InvitationScreen|Invitation was not sent')), registrationUrl ? /*#__PURE__*/_react.default.createElement(CopyableRegistrationUrl, {
125
- registrationUrl: registrationUrl
126
- }) : /*#__PURE__*/_react.default.createElement(_reactstrap.FormText, null, t('InvitationScreen|The user could not be invited')), /*#__PURE__*/_react.default.createElement("div", {
127
- className: "mt-2"
128
- }, /*#__PURE__*/_react.default.createElement(_reactstrap.Button, {
129
- onClick: () => setIsInvitationSuccessful(undefined),
130
- color: "primary",
131
- size: "lg"
132
- }, t('General|Back'))));
117
+ size: "lg"
118
+ }, t('General|Continue'))));
119
+ }
133
120
  return /*#__PURE__*/_react.default.createElement(_reactstrap.Container, {
134
- fluid: true,
135
- className: isInvitationSuccessful == undefined ? '' : 'h-100'
136
- }, isInvitationSuccessful != undefined ? /*#__PURE__*/_react.default.createElement(_asab_webui_components.ResultCard, {
137
- status: isInvitationSuccessful ? 'success' : 'danger'
138
- }, isInvitationSuccessful ? /*#__PURE__*/_react.default.createElement(SuccessfulInvitationCardBody, null) : /*#__PURE__*/_react.default.createElement(UnsuccessfulInvitationCardBody, null)) : /*#__PURE__*/_react.default.createElement(_reactstrap.Row, {
121
+ fluid: true
122
+ }, /*#__PURE__*/_react.default.createElement(_reactstrap.Row, {
139
123
  className: "justify-content-center pt-5"
140
124
  }, /*#__PURE__*/_react.default.createElement(_reactstrap.Col, {
141
125
  md: "4"
142
126
  }, /*#__PURE__*/_react.default.createElement(_reactstrap.Form, {
143
- onSubmit: e => {
144
- sendInvitation(e);
145
- }
127
+ onSubmit: sendInvitation
146
128
  }, /*#__PURE__*/_react.default.createElement(_reactstrap.Card, {
147
129
  className: "invite-card"
148
130
  }, /*#__PURE__*/_react.default.createElement(_reactstrap.CardHeader, {
@@ -165,9 +147,7 @@ function InvitationScreen(props) {
165
147
  autoComplete: "email",
166
148
  autoFocus: true,
167
149
  value: emailValue,
168
- onChange: e => {
169
- emailValidation(e);
170
- }
150
+ onChange: handleEmailChange
171
151
  }))), /*#__PURE__*/_react.default.createElement(_reactstrap.CardFooter, {
172
152
  className: "card-footer-flex"
173
153
  }, /*#__PURE__*/_react.default.createElement(_reactstrap.Button, {
@@ -179,6 +159,6 @@ function InvitationScreen(props) {
179
159
  className: "flex-fill"
180
160
  }, "\xA0"), /*#__PURE__*/_react.default.createElement(_reactstrap.Button, {
181
161
  color: "primary",
182
- disabled: emailValue === '' // Disable button if input is empty
162
+ disabled: emailValue === '' || isSubmitting // Disable button if input is empty or request is in flight
183
163
  }, t('InvitationScreen|Invite'))))))));
184
164
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "asab_webui_shell",
3
- "version": "27.3.11",
3
+ "version": "27.3.13",
4
4
  "license": "BSD-3-Clause",
5
5
  "description": "TeskaLabs ASAB WebUI Shell Application",
6
6
  "contributors": [