@torq-north/react-tools 1.0.0 → 1.1.1
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/README.md +28 -2
- package/dist/index.cjs +36 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.es.js +36 -10
- package/dist/index.es.js.map +1 -1
- package/dist/src/Core/Api/apiHelpers.d.ts +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# Torq Tools
|
|
1
|
+
# Torq North Tools
|
|
2
2
|
|
|
3
|
-
A collection of react components, hooks and other utilities used in various
|
|
3
|
+
A collection of react components, hooks and other utilities used in various projects
|
|
4
4
|
|
|
5
5
|
## Getting it running
|
|
6
6
|
|
|
@@ -8,6 +8,32 @@ Upon pulling down the repo, in order to get the component browser running, all y
|
|
|
8
8
|
|
|
9
9
|

|
|
10
10
|
|
|
11
|
+
## Session expiry redirects
|
|
12
|
+
|
|
13
|
+
The API helpers (`sendGet`, `sendPost`, etc.) can automatically detect when a user's session has expired and redirect them to the login page. This is triggered when any API call receives a 401 response, or when the server issues a redirect to `/auth/login`, which is the typical behaviour of a Symfony OAuth firewall that redirects rather than returning 401 directly.
|
|
14
|
+
|
|
15
|
+
This behaviour is opt-in. Call `enableAuthRedirect` once at app startup with the login URL to activate it:
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { enableAuthRedirect } from "@torq-north/react-tools";
|
|
19
|
+
|
|
20
|
+
enableAuthRedirect("/auth/login");
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
If `enableAuthRedirect` is never called, session expiry responses are left for the call site to handle as normal errors.
|
|
24
|
+
|
|
25
|
+
### Dev environments
|
|
26
|
+
|
|
27
|
+
In local development the frontend and backend often run on different ports. If the backend is on a different port than the Vite dev server, navigating to `/auth/login` will hit Vite instead of the backend and the login page won't render correctly. Pass the full backend URL for dev:
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
enableAuthRedirect(
|
|
31
|
+
import.meta.env.DEV ? "http://localhost:8103/auth/login" : "/auth/login"
|
|
32
|
+
);
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Replace `8103` with whichever port your backend is exposed on locally. In production the relative URL is used and no special configuration is needed.
|
|
36
|
+
|
|
11
37
|
## Adding your own component
|
|
12
38
|
|
|
13
39
|
1. Copy/paste in component from IRC project
|
package/dist/index.cjs
CHANGED
|
@@ -146,8 +146,12 @@ var sendGet = function (url, params) { return __awaiter(void 0, void 0, void 0,
|
|
|
146
146
|
switch (_a.label) {
|
|
147
147
|
case 0:
|
|
148
148
|
params &&
|
|
149
|
-
Object.keys(params).forEach(function (key) {
|
|
150
|
-
|
|
149
|
+
Object.keys(params).forEach(function (key) {
|
|
150
|
+
return params[key] === undefined && delete params[key];
|
|
151
|
+
});
|
|
152
|
+
fetchUrl = params
|
|
153
|
+
? "".concat(url, "?").concat(new URLSearchParams(params).toString())
|
|
154
|
+
: url;
|
|
151
155
|
return [4 /*yield*/, fetch(fetchUrl)];
|
|
152
156
|
case 1:
|
|
153
157
|
response = _a.sent();
|
|
@@ -248,11 +252,29 @@ function isJson(response) {
|
|
|
248
252
|
}
|
|
249
253
|
return contentType.indexOf("application/json") >= 0;
|
|
250
254
|
}
|
|
255
|
+
var authLoginUrl = "/auth/login";
|
|
256
|
+
var authRedirectEnabled = false;
|
|
257
|
+
function enableAuthRedirect(loginUrl) {
|
|
258
|
+
if (loginUrl === void 0) { loginUrl = "/auth/login"; }
|
|
259
|
+
authLoginUrl = loginUrl;
|
|
260
|
+
authRedirectEnabled = true;
|
|
261
|
+
}
|
|
262
|
+
function isSessionExpired(response) {
|
|
263
|
+
return (response.status === 401 ||
|
|
264
|
+
(response.redirected && response.url.includes("/auth/login")));
|
|
265
|
+
}
|
|
251
266
|
function process$1(response) {
|
|
252
267
|
return __awaiter(this, void 0, void 0, function () {
|
|
253
268
|
return __generator(this, function (_a) {
|
|
254
269
|
switch (_a.label) {
|
|
255
|
-
case 0:
|
|
270
|
+
case 0:
|
|
271
|
+
if (authRedirectEnabled && isSessionExpired(response)) {
|
|
272
|
+
window.location.href = authLoginUrl;
|
|
273
|
+
// Return a never-resolving promise to suspend execution while the browser navigates away,
|
|
274
|
+
// preventing any error handling or UI updates from firing before the page unloads.
|
|
275
|
+
return [2 /*return*/, new Promise(function () { })];
|
|
276
|
+
}
|
|
277
|
+
return [4 /*yield*/, checkResponse(response)];
|
|
256
278
|
case 1:
|
|
257
279
|
_a.sent();
|
|
258
280
|
if (isJson(response)) {
|
|
@@ -770,7 +792,11 @@ var ErrorNotification = function (_a) {
|
|
|
770
792
|
return (jsxRuntime.jsx(material.Snackbar, { open: open, autoHideDuration: 6000, onClose: onClose, children: jsxRuntime.jsx(material.Alert, { onClose: onClose, severity: "error", sx: { width: "100%" }, elevation: 6, variant: "filled", children: message !== null && message !== void 0 ? message : "An error has occured" }) }));
|
|
771
793
|
};
|
|
772
794
|
|
|
773
|
-
var DropIndicator = material.styled(material.Box
|
|
795
|
+
var DropIndicator = material.styled(material.Box, {
|
|
796
|
+
shouldForwardProp: function (propName) {
|
|
797
|
+
return propName != "dragOver" && propName != "multiple" && propName != "disabled";
|
|
798
|
+
},
|
|
799
|
+
})(function (_a) {
|
|
774
800
|
var theme = _a.theme, dragOver = _a.dragOver, multiple = _a.multiple, disabled = _a.disabled;
|
|
775
801
|
return ({
|
|
776
802
|
padding: dragOver ? "1px" : "4px",
|
|
@@ -779,7 +805,7 @@ var DropIndicator = material.styled(material.Box)(function (_a) {
|
|
|
779
805
|
borderRadius: 1,
|
|
780
806
|
cursor: multiple ? undefined : "pointer",
|
|
781
807
|
transition: "0.2s opacity",
|
|
782
|
-
"&:hover": disabled
|
|
808
|
+
"&:hover": disabled || multiple
|
|
783
809
|
? undefined
|
|
784
810
|
: {
|
|
785
811
|
opacity: 0.8,
|
|
@@ -4780,7 +4806,7 @@ function useFileEditorField(name, options) {
|
|
|
4780
4806
|
var _c = React.useState(""), errorMessage = _c[0], setErrorMessage = _c[1];
|
|
4781
4807
|
var _d = React.useState(false), dragOverDropzone = _d[0], setDragOverDropzone = _d[1];
|
|
4782
4808
|
var multiple = options.multiple, disabled = options.disabled, otherOptions = __rest(options, ["multiple", "disabled"]);
|
|
4783
|
-
var _e = useDropzone(__assign({ multiple: multiple, noClick: multiple, noKeyboard: multiple, disabled: disabled, onDragEnter: function () { return setDragOverDropzone(true); }, onDragOver: function (e) {
|
|
4809
|
+
var _e = useDropzone(__assign({ multiple: multiple, noClick: multiple || values.length == 0, noKeyboard: multiple, disabled: disabled, onDragEnter: function () { return setDragOverDropzone(true); }, onDragOver: function (e) {
|
|
4784
4810
|
e.preventDefault();
|
|
4785
4811
|
e.stopPropagation();
|
|
4786
4812
|
}, onDragLeave: function () { return setDragOverDropzone(false); }, onDrop: function () { return setDragOverDropzone(false); }, onDropAccepted: function (acceptedFiles) {
|
|
@@ -4883,9 +4909,9 @@ function IndividualFile(_a) {
|
|
|
4883
4909
|
function FileDisplay(_a) {
|
|
4884
4910
|
var files = _a.files, individualFileProps = __rest(_a, ["files"]);
|
|
4885
4911
|
if (!Array.isArray(files)) {
|
|
4886
|
-
return (jsxRuntime.jsx(material.Box, {
|
|
4912
|
+
return (jsxRuntime.jsx(material.Box, { children: jsxRuntime.jsx(IndividualFile, __assign({ file: files }, individualFileProps)) }));
|
|
4887
4913
|
}
|
|
4888
|
-
return (jsxRuntime.jsx(material.Stack, {
|
|
4914
|
+
return (jsxRuntime.jsx(material.Stack, { children: files.map(function (f) { return (jsxRuntime.jsx(IndividualFile, __assign({ file: f }, individualFileProps), isAsset(f) ? f.id : f.name)); }) }));
|
|
4889
4915
|
}
|
|
4890
4916
|
|
|
4891
4917
|
function FileEditorField(_a) {
|
|
@@ -4897,7 +4923,7 @@ function FileEditorField(_a) {
|
|
|
4897
4923
|
required: required,
|
|
4898
4924
|
fieldProps: slotProps === null || slotProps === void 0 ? void 0 : slotProps.field,
|
|
4899
4925
|
}), getInputProps = _b.getInputProps, getRootProps = _b.getRootProps, dragOverDropzone = _b.dragOverDropzone, values = _b.values, removeFile = _b.removeFile, open = _b.open, showError = _b.showError, setShowError = _b.setShowError, errorMessage = _b.errorMessage, meta = _b.meta;
|
|
4900
|
-
return (jsxRuntime.jsxs(FieldWrapper, { icon: icon, showSpace: showSpace, children: [jsxRuntime.jsxs(material.FormControl, { fullWidth: true, sx: { position: "relative" }, children: [label && (jsxRuntime.jsx(material.FormLabel, { disabled: disabled, error: meta.touched && meta.error, children: label })), jsxRuntime.jsx("input", __assign({}, getInputProps())), jsxRuntime.
|
|
4926
|
+
return (jsxRuntime.jsxs(FieldWrapper, { icon: icon, showSpace: showSpace, children: [jsxRuntime.jsxs(material.FormControl, { fullWidth: true, sx: { position: "relative" }, children: [label && (jsxRuntime.jsx(material.FormLabel, { disabled: disabled, error: meta.touched && meta.error, children: label })), jsxRuntime.jsx("input", __assign({}, getInputProps())), jsxRuntime.jsx(DropIndicator, __assign({ multiple: multiple, dragOver: dragOverDropzone, disabled: disabled }, getRootProps(), { children: jsxRuntime.jsxs(material.Stack, { gap: 1, children: [values.length > 0 && (jsxRuntime.jsx(material.Box, { sx: { opacity: disabled ? 0.4 : 1 }, children: jsxRuntime.jsx(FileDisplay, { files: values !== null && values !== void 0 ? values : [], disableLink: true, allowDelete: !disabled, onDelete: removeFile }) })), (multiple || (values === null || values === void 0 ? void 0 : values.length) == 0) && (jsxRuntime.jsx(material.Button, { onClick: open, startIcon: jsxRuntime.jsx(AddIcon, {}), fullWidth: true, disabled: disabled, children: placeholder !== null && placeholder !== void 0 ? placeholder : "Add File".concat(multiple ? "s" : "") }))] }) })), dragOverDropzone && (jsxRuntime.jsx(material.Stack, { position: "absolute", top: 0, bottom: 0, left: 0, right: 0, justifyContent: "center", alignItems: "center", sx: { pointerEvents: "none" }, children: jsxRuntime.jsx(NoteAddIcon, { sx: { fontSize: 50 } }) })), jsxRuntime.jsx(material.FormHelperText, { error: meta.touched && meta.error, children: (meta.touched && meta.error) || helperText })] }), jsxRuntime.jsx(ErrorNotification, { open: showError, onClose: function () { return setShowError(false); }, message: errorMessage })] }));
|
|
4901
4927
|
}
|
|
4902
4928
|
|
|
4903
4929
|
function asInteger(value) {
|
|
@@ -5205,6 +5231,7 @@ exports.TabSet = TabSet;
|
|
|
5205
5231
|
exports.TextFormField = TextFormField;
|
|
5206
5232
|
exports.asInteger = asInteger;
|
|
5207
5233
|
exports.asPhoneNumber = asPhoneNumber;
|
|
5234
|
+
exports.enableAuthRedirect = enableAuthRedirect;
|
|
5208
5235
|
exports.isEmail = isEmail;
|
|
5209
5236
|
exports.isPostalCode = isPostalCode;
|
|
5210
5237
|
exports.sendDelete = sendDelete;
|