strapi-plugin-magic-link-v5 5.5.9 → 6.0.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 +733 -0
- package/README.md +662 -928
- package/dist/admin/LicenseGuard-BgLA9go3.mjs +495 -0
- package/dist/admin/LicenseGuard-ewCjpc17.js +513 -0
- package/dist/admin/de-B1FNErPH.mjs +673 -0
- package/dist/admin/de-e75OECdq.js +673 -0
- package/dist/admin/en-CABKGbxH.js +673 -0
- package/dist/admin/en-HqDOFBCc.mjs +673 -0
- package/dist/admin/es-DpWl0Fax.js +673 -0
- package/dist/admin/es-jb94jqPR.mjs +673 -0
- package/dist/admin/fr-BQhLPvYw.js +673 -0
- package/dist/admin/fr-BseH1wrr.mjs +673 -0
- package/dist/admin/index-6ot9j1cN.js +5268 -0
- package/dist/admin/index-B390rccl.js +622 -0
- package/dist/admin/index-BPGKtruf.mjs +5266 -0
- package/dist/admin/index-BaTIQtnA.js +3869 -0
- package/dist/admin/index-CmG-93yx.js +141 -0
- package/dist/admin/index-DXe_ge4v.mjs +620 -0
- package/dist/admin/index-Dv5PVT1E.mjs +3867 -0
- package/dist/admin/index-k240ls6p.mjs +142 -0
- package/dist/admin/index.js +3 -3
- package/dist/admin/index.mjs +1 -2
- package/dist/admin/pt-CPkaveti.js +673 -0
- package/dist/admin/pt-DhlSCJn2.mjs +673 -0
- package/dist/admin/useUnsavedChangesGuard-BVZ4Mg4E.js +225 -0
- package/dist/admin/useUnsavedChangesGuard-QnkiZTLP.mjs +224 -0
- package/dist/server/i18n/de.json +32 -1
- package/dist/server/i18n/en.json +32 -1
- package/dist/server/i18n/es.json +32 -1
- package/dist/server/i18n/fr.json +32 -1
- package/dist/server/i18n/pt.json +32 -1
- package/dist/server/index.js +7092 -5971
- package/dist/server/index.mjs +7115 -5972
- package/package.json +42 -41
- package/strapi-admin.js +3 -1
- package/strapi-server.js +3 -2
- package/dist/_chunks/LicenseGuard-CiyjFnrw.js +0 -54
- package/dist/_chunks/LicenseGuard-DJ5fq2UZ.mjs +0 -55
- package/dist/_chunks/de-BSOk5suQ.js +0 -454
- package/dist/_chunks/de-CwhYs3O3.mjs +0 -454
- package/dist/_chunks/en-DnvSpXvu.js +0 -454
- package/dist/_chunks/en-gXXgGDV1.mjs +0 -454
- package/dist/_chunks/es-B-tnwByt.js +0 -407
- package/dist/_chunks/es-DG5gW3RZ.mjs +0 -407
- package/dist/_chunks/fr-BKo8t7Fx.js +0 -407
- package/dist/_chunks/fr-BQn1OHOR.mjs +0 -407
- package/dist/_chunks/index-01GHNxSr.js +0 -307
- package/dist/_chunks/index-5jsDgD-q.mjs +0 -330
- package/dist/_chunks/index-BWuj-5WE.js +0 -5068
- package/dist/_chunks/index-Bj3H_Z4B.js +0 -142
- package/dist/_chunks/index-CC4Eu9Dm.js +0 -332
- package/dist/_chunks/index-Csyi13vd.mjs +0 -2891
- package/dist/_chunks/index-DJqXiiP1.js +0 -2893
- package/dist/_chunks/index-DQUYFsLT.mjs +0 -143
- package/dist/_chunks/index-Dufv-daf.mjs +0 -305
- package/dist/_chunks/index-szeYJKpN.mjs +0 -5066
- package/dist/_chunks/pt-CjEkXBAz.js +0 -407
- package/dist/_chunks/pt-trziAOzT.mjs +0 -407
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const jsxRuntime = require("react/jsx-runtime");
|
|
3
|
+
const react = require("react");
|
|
4
|
+
const designSystem = require("@strapi/design-system");
|
|
5
|
+
const icons = require("@strapi/icons");
|
|
6
|
+
const styled = require("styled-components");
|
|
7
|
+
const LicenseGuard = require("./LicenseGuard-ewCjpc17.js");
|
|
8
|
+
const reactRouterDom = require("react-router-dom");
|
|
9
|
+
const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
|
|
10
|
+
const styled__default = /* @__PURE__ */ _interopDefault(styled);
|
|
11
|
+
const useReturnFocus = (isOpen, getFallback) => {
|
|
12
|
+
const openerRef = react.useRef(null);
|
|
13
|
+
const fallbackRef = react.useRef(getFallback);
|
|
14
|
+
react.useLayoutEffect(() => {
|
|
15
|
+
fallbackRef.current = getFallback;
|
|
16
|
+
}, [getFallback]);
|
|
17
|
+
react.useLayoutEffect(() => {
|
|
18
|
+
if (!isOpen) return void 0;
|
|
19
|
+
const active = document.activeElement;
|
|
20
|
+
const opener = active && active !== document.body ? active : null;
|
|
21
|
+
openerRef.current = opener;
|
|
22
|
+
return () => {
|
|
23
|
+
window.setTimeout(() => {
|
|
24
|
+
const current = document.activeElement;
|
|
25
|
+
if (current && current !== document.body) return;
|
|
26
|
+
const target = opener?.isConnected ? opener : fallbackRef.current?.();
|
|
27
|
+
target?.focus?.();
|
|
28
|
+
}, 0);
|
|
29
|
+
};
|
|
30
|
+
}, [isOpen]);
|
|
31
|
+
return react.useCallback((event) => {
|
|
32
|
+
const opener = openerRef.current;
|
|
33
|
+
openerRef.current = null;
|
|
34
|
+
const target = opener?.isConnected ? opener : fallbackRef.current?.();
|
|
35
|
+
if (!target || typeof target.focus !== "function") return;
|
|
36
|
+
event.preventDefault();
|
|
37
|
+
target.focus();
|
|
38
|
+
}, []);
|
|
39
|
+
};
|
|
40
|
+
const WarningIcon = styled__default.default(icons.WarningCircle)`
|
|
41
|
+
width: 24px;
|
|
42
|
+
height: 24px;
|
|
43
|
+
|
|
44
|
+
path {
|
|
45
|
+
fill: ${(p) => p.$danger ? LicenseGuard.accent.danger(p) : LicenseGuard.accent.warning(p)};
|
|
46
|
+
}
|
|
47
|
+
`;
|
|
48
|
+
const ConfirmDialog = ({
|
|
49
|
+
isOpen,
|
|
50
|
+
onClose,
|
|
51
|
+
onConfirm,
|
|
52
|
+
title,
|
|
53
|
+
description,
|
|
54
|
+
children,
|
|
55
|
+
confirmLabel,
|
|
56
|
+
cancelLabel,
|
|
57
|
+
variant = "danger",
|
|
58
|
+
isLoading = false,
|
|
59
|
+
confirmIcon,
|
|
60
|
+
getReturnFocusFallback
|
|
61
|
+
}) => {
|
|
62
|
+
const { t } = LicenseGuard.useMagicLinkIntl();
|
|
63
|
+
const [isPending, setIsPending] = react.useState(false);
|
|
64
|
+
const mounted = react.useRef(true);
|
|
65
|
+
const returnFocus = useReturnFocus(Boolean(isOpen), getReturnFocusFallback);
|
|
66
|
+
react.useEffect(() => {
|
|
67
|
+
mounted.current = true;
|
|
68
|
+
return () => {
|
|
69
|
+
mounted.current = false;
|
|
70
|
+
};
|
|
71
|
+
}, []);
|
|
72
|
+
const busy = isLoading || isPending;
|
|
73
|
+
const danger = variant === "danger";
|
|
74
|
+
const handleConfirm = async () => {
|
|
75
|
+
if (busy) return;
|
|
76
|
+
const result = onConfirm?.();
|
|
77
|
+
if (result && typeof result.then === "function") {
|
|
78
|
+
setIsPending(true);
|
|
79
|
+
try {
|
|
80
|
+
await result;
|
|
81
|
+
} catch {
|
|
82
|
+
} finally {
|
|
83
|
+
if (mounted.current) setIsPending(false);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
const handleOpenChange = (open) => {
|
|
88
|
+
if (!open && !busy) onClose?.();
|
|
89
|
+
};
|
|
90
|
+
return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Root, { open: Boolean(isOpen), onOpenChange: handleOpenChange, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Dialog.Content, { onCloseAutoFocus: returnFocus, children: [
|
|
91
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Header, { textAlign: "center", children: title }),
|
|
92
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Body, { icon: /* @__PURE__ */ jsxRuntime.jsx(WarningIcon, { "aria-hidden": "true", $danger: danger }), children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 2, width: "100%", alignItems: "stretch", children: [
|
|
93
|
+
description ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Description, { textAlign: "center", children: description }) : null,
|
|
94
|
+
typeof children === "string" ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", textAlign: "center", children }) : children
|
|
95
|
+
] }) }),
|
|
96
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Dialog.Footer, { children: [
|
|
97
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Cancel, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "tertiary", fullWidth: true, disabled: busy, children: cancelLabel ?? t("common.cancel", "Cancel") }) }),
|
|
98
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
99
|
+
designSystem.Button,
|
|
100
|
+
{
|
|
101
|
+
variant: danger ? "danger" : "default",
|
|
102
|
+
fullWidth: true,
|
|
103
|
+
onClick: handleConfirm,
|
|
104
|
+
loading: busy,
|
|
105
|
+
startIcon: busy ? void 0 : confirmIcon,
|
|
106
|
+
children: confirmLabel ?? (danger ? t("common.delete", "Delete") : t("common.confirm", "Confirm"))
|
|
107
|
+
}
|
|
108
|
+
)
|
|
109
|
+
] })
|
|
110
|
+
] }) });
|
|
111
|
+
};
|
|
112
|
+
const UnsavedChangesDialog = ({
|
|
113
|
+
guard,
|
|
114
|
+
title,
|
|
115
|
+
description,
|
|
116
|
+
confirmLabel,
|
|
117
|
+
getReturnFocusFallback
|
|
118
|
+
}) => {
|
|
119
|
+
const { t } = LicenseGuard.useMagicLinkIntl();
|
|
120
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
121
|
+
ConfirmDialog,
|
|
122
|
+
{
|
|
123
|
+
isOpen: guard.isOpen,
|
|
124
|
+
onClose: guard.cancel,
|
|
125
|
+
onConfirm: guard.confirm,
|
|
126
|
+
variant: "danger",
|
|
127
|
+
title: title ?? t("common.unsaved.title", "Discard unsaved changes?"),
|
|
128
|
+
description: description ?? t(
|
|
129
|
+
"common.unsaved.description",
|
|
130
|
+
"You have unsaved changes. If you continue, they will be lost."
|
|
131
|
+
),
|
|
132
|
+
confirmLabel: confirmLabel ?? t("common.unsaved.confirm", "Discard changes"),
|
|
133
|
+
cancelLabel: t("common.unsaved.cancel", "Keep editing"),
|
|
134
|
+
getReturnFocusFallback
|
|
135
|
+
}
|
|
136
|
+
);
|
|
137
|
+
};
|
|
138
|
+
const shouldBlockNavigation = (isDirty, transition = {}) => {
|
|
139
|
+
if (!isDirty) return false;
|
|
140
|
+
const { currentLocation, nextLocation } = transition;
|
|
141
|
+
if (!nextLocation) return false;
|
|
142
|
+
return currentLocation?.pathname !== nextLocation.pathname || (currentLocation?.search ?? "") !== (nextLocation.search ?? "");
|
|
143
|
+
};
|
|
144
|
+
const attachBeforeUnload = (active, win = typeof window === "undefined" ? void 0 : window) => {
|
|
145
|
+
if (!active || typeof win?.addEventListener !== "function") return () => {
|
|
146
|
+
};
|
|
147
|
+
const handler = (event) => {
|
|
148
|
+
event.preventDefault();
|
|
149
|
+
event.returnValue = "";
|
|
150
|
+
return "";
|
|
151
|
+
};
|
|
152
|
+
win.addEventListener("beforeunload", handler);
|
|
153
|
+
return () => win.removeEventListener("beforeunload", handler);
|
|
154
|
+
};
|
|
155
|
+
const useDiscardState = (isDirty) => {
|
|
156
|
+
const dirtyRef = react.useRef(Boolean(isDirty));
|
|
157
|
+
react.useLayoutEffect(() => {
|
|
158
|
+
dirtyRef.current = Boolean(isDirty);
|
|
159
|
+
}, [isDirty]);
|
|
160
|
+
const [pendingAction, setPendingAction] = react.useState(null);
|
|
161
|
+
const runGuarded = react.useCallback((action) => {
|
|
162
|
+
if (typeof action !== "function") return;
|
|
163
|
+
if (dirtyRef.current) {
|
|
164
|
+
setPendingAction(() => action);
|
|
165
|
+
} else {
|
|
166
|
+
action();
|
|
167
|
+
}
|
|
168
|
+
}, []);
|
|
169
|
+
return { dirtyRef, pendingAction, setPendingAction, runGuarded };
|
|
170
|
+
};
|
|
171
|
+
const useDiscardGuard = (isDirty) => {
|
|
172
|
+
const { pendingAction, setPendingAction, runGuarded } = useDiscardState(isDirty);
|
|
173
|
+
const confirm = react.useCallback(() => {
|
|
174
|
+
const action = pendingAction;
|
|
175
|
+
setPendingAction(null);
|
|
176
|
+
if (action) action();
|
|
177
|
+
}, [pendingAction, setPendingAction]);
|
|
178
|
+
const cancel = react.useCallback(() => setPendingAction(null), [setPendingAction]);
|
|
179
|
+
return { isDirty: Boolean(isDirty), isOpen: pendingAction !== null, confirm, cancel, runGuarded };
|
|
180
|
+
};
|
|
181
|
+
const useUnsavedChangesGuard = (isDirty) => {
|
|
182
|
+
const { dirtyRef, pendingAction, setPendingAction, runGuarded } = useDiscardState(isDirty);
|
|
183
|
+
const bypassRef = react.useRef(false);
|
|
184
|
+
const blocker = reactRouterDom.useBlocker(
|
|
185
|
+
react.useCallback(
|
|
186
|
+
(transition) => !bypassRef.current && shouldBlockNavigation(dirtyRef.current, transition),
|
|
187
|
+
[dirtyRef]
|
|
188
|
+
)
|
|
189
|
+
);
|
|
190
|
+
react.useEffect(() => attachBeforeUnload(Boolean(isDirty)), [isDirty]);
|
|
191
|
+
react.useEffect(() => {
|
|
192
|
+
if (blocker.state === "blocked" && !isDirty) blocker.proceed();
|
|
193
|
+
}, [blocker, isDirty]);
|
|
194
|
+
const confirm = react.useCallback(() => {
|
|
195
|
+
if (blocker.state === "blocked") blocker.proceed();
|
|
196
|
+
const action = pendingAction;
|
|
197
|
+
setPendingAction(null);
|
|
198
|
+
if (action) action();
|
|
199
|
+
}, [blocker, pendingAction, setPendingAction]);
|
|
200
|
+
const cancel = react.useCallback(() => {
|
|
201
|
+
if (blocker.state === "blocked") blocker.reset();
|
|
202
|
+
setPendingAction(null);
|
|
203
|
+
}, [blocker, setPendingAction]);
|
|
204
|
+
const bypass = react.useCallback((action) => {
|
|
205
|
+
bypassRef.current = true;
|
|
206
|
+
try {
|
|
207
|
+
return action();
|
|
208
|
+
} finally {
|
|
209
|
+
bypassRef.current = false;
|
|
210
|
+
}
|
|
211
|
+
}, []);
|
|
212
|
+
return {
|
|
213
|
+
isDirty: Boolean(isDirty),
|
|
214
|
+
isOpen: blocker.state === "blocked" || pendingAction !== null,
|
|
215
|
+
confirm,
|
|
216
|
+
cancel,
|
|
217
|
+
runGuarded,
|
|
218
|
+
bypass
|
|
219
|
+
};
|
|
220
|
+
};
|
|
221
|
+
exports.ConfirmDialog = ConfirmDialog;
|
|
222
|
+
exports.UnsavedChangesDialog = UnsavedChangesDialog;
|
|
223
|
+
exports.useDiscardGuard = useDiscardGuard;
|
|
224
|
+
exports.useReturnFocus = useReturnFocus;
|
|
225
|
+
exports.useUnsavedChangesGuard = useUnsavedChangesGuard;
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useRef, useLayoutEffect, useCallback, useState, useEffect } from "react";
|
|
3
|
+
import { Dialog, Flex, Typography, Button } from "@strapi/design-system";
|
|
4
|
+
import { WarningCircle } from "@strapi/icons";
|
|
5
|
+
import styled from "styled-components";
|
|
6
|
+
import { u as useMagicLinkIntl, d as accent } from "./LicenseGuard-BgLA9go3.mjs";
|
|
7
|
+
import { useBlocker } from "react-router-dom";
|
|
8
|
+
const useReturnFocus = (isOpen, getFallback) => {
|
|
9
|
+
const openerRef = useRef(null);
|
|
10
|
+
const fallbackRef = useRef(getFallback);
|
|
11
|
+
useLayoutEffect(() => {
|
|
12
|
+
fallbackRef.current = getFallback;
|
|
13
|
+
}, [getFallback]);
|
|
14
|
+
useLayoutEffect(() => {
|
|
15
|
+
if (!isOpen) return void 0;
|
|
16
|
+
const active = document.activeElement;
|
|
17
|
+
const opener = active && active !== document.body ? active : null;
|
|
18
|
+
openerRef.current = opener;
|
|
19
|
+
return () => {
|
|
20
|
+
window.setTimeout(() => {
|
|
21
|
+
const current = document.activeElement;
|
|
22
|
+
if (current && current !== document.body) return;
|
|
23
|
+
const target = opener?.isConnected ? opener : fallbackRef.current?.();
|
|
24
|
+
target?.focus?.();
|
|
25
|
+
}, 0);
|
|
26
|
+
};
|
|
27
|
+
}, [isOpen]);
|
|
28
|
+
return useCallback((event) => {
|
|
29
|
+
const opener = openerRef.current;
|
|
30
|
+
openerRef.current = null;
|
|
31
|
+
const target = opener?.isConnected ? opener : fallbackRef.current?.();
|
|
32
|
+
if (!target || typeof target.focus !== "function") return;
|
|
33
|
+
event.preventDefault();
|
|
34
|
+
target.focus();
|
|
35
|
+
}, []);
|
|
36
|
+
};
|
|
37
|
+
const WarningIcon = styled(WarningCircle)`
|
|
38
|
+
width: 24px;
|
|
39
|
+
height: 24px;
|
|
40
|
+
|
|
41
|
+
path {
|
|
42
|
+
fill: ${(p) => p.$danger ? accent.danger(p) : accent.warning(p)};
|
|
43
|
+
}
|
|
44
|
+
`;
|
|
45
|
+
const ConfirmDialog = ({
|
|
46
|
+
isOpen,
|
|
47
|
+
onClose,
|
|
48
|
+
onConfirm,
|
|
49
|
+
title,
|
|
50
|
+
description,
|
|
51
|
+
children,
|
|
52
|
+
confirmLabel,
|
|
53
|
+
cancelLabel,
|
|
54
|
+
variant = "danger",
|
|
55
|
+
isLoading = false,
|
|
56
|
+
confirmIcon,
|
|
57
|
+
getReturnFocusFallback
|
|
58
|
+
}) => {
|
|
59
|
+
const { t } = useMagicLinkIntl();
|
|
60
|
+
const [isPending, setIsPending] = useState(false);
|
|
61
|
+
const mounted = useRef(true);
|
|
62
|
+
const returnFocus = useReturnFocus(Boolean(isOpen), getReturnFocusFallback);
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
mounted.current = true;
|
|
65
|
+
return () => {
|
|
66
|
+
mounted.current = false;
|
|
67
|
+
};
|
|
68
|
+
}, []);
|
|
69
|
+
const busy = isLoading || isPending;
|
|
70
|
+
const danger = variant === "danger";
|
|
71
|
+
const handleConfirm = async () => {
|
|
72
|
+
if (busy) return;
|
|
73
|
+
const result = onConfirm?.();
|
|
74
|
+
if (result && typeof result.then === "function") {
|
|
75
|
+
setIsPending(true);
|
|
76
|
+
try {
|
|
77
|
+
await result;
|
|
78
|
+
} catch {
|
|
79
|
+
} finally {
|
|
80
|
+
if (mounted.current) setIsPending(false);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
const handleOpenChange = (open) => {
|
|
85
|
+
if (!open && !busy) onClose?.();
|
|
86
|
+
};
|
|
87
|
+
return /* @__PURE__ */ jsx(Dialog.Root, { open: Boolean(isOpen), onOpenChange: handleOpenChange, children: /* @__PURE__ */ jsxs(Dialog.Content, { onCloseAutoFocus: returnFocus, children: [
|
|
88
|
+
/* @__PURE__ */ jsx(Dialog.Header, { textAlign: "center", children: title }),
|
|
89
|
+
/* @__PURE__ */ jsx(Dialog.Body, { icon: /* @__PURE__ */ jsx(WarningIcon, { "aria-hidden": "true", $danger: danger }), children: /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 2, width: "100%", alignItems: "stretch", children: [
|
|
90
|
+
description ? /* @__PURE__ */ jsx(Dialog.Description, { textAlign: "center", children: description }) : null,
|
|
91
|
+
typeof children === "string" ? /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", textAlign: "center", children }) : children
|
|
92
|
+
] }) }),
|
|
93
|
+
/* @__PURE__ */ jsxs(Dialog.Footer, { children: [
|
|
94
|
+
/* @__PURE__ */ jsx(Dialog.Cancel, { children: /* @__PURE__ */ jsx(Button, { variant: "tertiary", fullWidth: true, disabled: busy, children: cancelLabel ?? t("common.cancel", "Cancel") }) }),
|
|
95
|
+
/* @__PURE__ */ jsx(
|
|
96
|
+
Button,
|
|
97
|
+
{
|
|
98
|
+
variant: danger ? "danger" : "default",
|
|
99
|
+
fullWidth: true,
|
|
100
|
+
onClick: handleConfirm,
|
|
101
|
+
loading: busy,
|
|
102
|
+
startIcon: busy ? void 0 : confirmIcon,
|
|
103
|
+
children: confirmLabel ?? (danger ? t("common.delete", "Delete") : t("common.confirm", "Confirm"))
|
|
104
|
+
}
|
|
105
|
+
)
|
|
106
|
+
] })
|
|
107
|
+
] }) });
|
|
108
|
+
};
|
|
109
|
+
const UnsavedChangesDialog = ({
|
|
110
|
+
guard,
|
|
111
|
+
title,
|
|
112
|
+
description,
|
|
113
|
+
confirmLabel,
|
|
114
|
+
getReturnFocusFallback
|
|
115
|
+
}) => {
|
|
116
|
+
const { t } = useMagicLinkIntl();
|
|
117
|
+
return /* @__PURE__ */ jsx(
|
|
118
|
+
ConfirmDialog,
|
|
119
|
+
{
|
|
120
|
+
isOpen: guard.isOpen,
|
|
121
|
+
onClose: guard.cancel,
|
|
122
|
+
onConfirm: guard.confirm,
|
|
123
|
+
variant: "danger",
|
|
124
|
+
title: title ?? t("common.unsaved.title", "Discard unsaved changes?"),
|
|
125
|
+
description: description ?? t(
|
|
126
|
+
"common.unsaved.description",
|
|
127
|
+
"You have unsaved changes. If you continue, they will be lost."
|
|
128
|
+
),
|
|
129
|
+
confirmLabel: confirmLabel ?? t("common.unsaved.confirm", "Discard changes"),
|
|
130
|
+
cancelLabel: t("common.unsaved.cancel", "Keep editing"),
|
|
131
|
+
getReturnFocusFallback
|
|
132
|
+
}
|
|
133
|
+
);
|
|
134
|
+
};
|
|
135
|
+
const shouldBlockNavigation = (isDirty, transition = {}) => {
|
|
136
|
+
if (!isDirty) return false;
|
|
137
|
+
const { currentLocation, nextLocation } = transition;
|
|
138
|
+
if (!nextLocation) return false;
|
|
139
|
+
return currentLocation?.pathname !== nextLocation.pathname || (currentLocation?.search ?? "") !== (nextLocation.search ?? "");
|
|
140
|
+
};
|
|
141
|
+
const attachBeforeUnload = (active, win = typeof window === "undefined" ? void 0 : window) => {
|
|
142
|
+
if (!active || typeof win?.addEventListener !== "function") return () => {
|
|
143
|
+
};
|
|
144
|
+
const handler = (event) => {
|
|
145
|
+
event.preventDefault();
|
|
146
|
+
event.returnValue = "";
|
|
147
|
+
return "";
|
|
148
|
+
};
|
|
149
|
+
win.addEventListener("beforeunload", handler);
|
|
150
|
+
return () => win.removeEventListener("beforeunload", handler);
|
|
151
|
+
};
|
|
152
|
+
const useDiscardState = (isDirty) => {
|
|
153
|
+
const dirtyRef = useRef(Boolean(isDirty));
|
|
154
|
+
useLayoutEffect(() => {
|
|
155
|
+
dirtyRef.current = Boolean(isDirty);
|
|
156
|
+
}, [isDirty]);
|
|
157
|
+
const [pendingAction, setPendingAction] = useState(null);
|
|
158
|
+
const runGuarded = useCallback((action) => {
|
|
159
|
+
if (typeof action !== "function") return;
|
|
160
|
+
if (dirtyRef.current) {
|
|
161
|
+
setPendingAction(() => action);
|
|
162
|
+
} else {
|
|
163
|
+
action();
|
|
164
|
+
}
|
|
165
|
+
}, []);
|
|
166
|
+
return { dirtyRef, pendingAction, setPendingAction, runGuarded };
|
|
167
|
+
};
|
|
168
|
+
const useDiscardGuard = (isDirty) => {
|
|
169
|
+
const { pendingAction, setPendingAction, runGuarded } = useDiscardState(isDirty);
|
|
170
|
+
const confirm = useCallback(() => {
|
|
171
|
+
const action = pendingAction;
|
|
172
|
+
setPendingAction(null);
|
|
173
|
+
if (action) action();
|
|
174
|
+
}, [pendingAction, setPendingAction]);
|
|
175
|
+
const cancel = useCallback(() => setPendingAction(null), [setPendingAction]);
|
|
176
|
+
return { isDirty: Boolean(isDirty), isOpen: pendingAction !== null, confirm, cancel, runGuarded };
|
|
177
|
+
};
|
|
178
|
+
const useUnsavedChangesGuard = (isDirty) => {
|
|
179
|
+
const { dirtyRef, pendingAction, setPendingAction, runGuarded } = useDiscardState(isDirty);
|
|
180
|
+
const bypassRef = useRef(false);
|
|
181
|
+
const blocker = useBlocker(
|
|
182
|
+
useCallback(
|
|
183
|
+
(transition) => !bypassRef.current && shouldBlockNavigation(dirtyRef.current, transition),
|
|
184
|
+
[dirtyRef]
|
|
185
|
+
)
|
|
186
|
+
);
|
|
187
|
+
useEffect(() => attachBeforeUnload(Boolean(isDirty)), [isDirty]);
|
|
188
|
+
useEffect(() => {
|
|
189
|
+
if (blocker.state === "blocked" && !isDirty) blocker.proceed();
|
|
190
|
+
}, [blocker, isDirty]);
|
|
191
|
+
const confirm = useCallback(() => {
|
|
192
|
+
if (blocker.state === "blocked") blocker.proceed();
|
|
193
|
+
const action = pendingAction;
|
|
194
|
+
setPendingAction(null);
|
|
195
|
+
if (action) action();
|
|
196
|
+
}, [blocker, pendingAction, setPendingAction]);
|
|
197
|
+
const cancel = useCallback(() => {
|
|
198
|
+
if (blocker.state === "blocked") blocker.reset();
|
|
199
|
+
setPendingAction(null);
|
|
200
|
+
}, [blocker, setPendingAction]);
|
|
201
|
+
const bypass = useCallback((action) => {
|
|
202
|
+
bypassRef.current = true;
|
|
203
|
+
try {
|
|
204
|
+
return action();
|
|
205
|
+
} finally {
|
|
206
|
+
bypassRef.current = false;
|
|
207
|
+
}
|
|
208
|
+
}, []);
|
|
209
|
+
return {
|
|
210
|
+
isDirty: Boolean(isDirty),
|
|
211
|
+
isOpen: blocker.state === "blocked" || pendingAction !== null,
|
|
212
|
+
confirm,
|
|
213
|
+
cancel,
|
|
214
|
+
runGuarded,
|
|
215
|
+
bypass
|
|
216
|
+
};
|
|
217
|
+
};
|
|
218
|
+
export {
|
|
219
|
+
ConfirmDialog as C,
|
|
220
|
+
UnsavedChangesDialog as U,
|
|
221
|
+
useDiscardGuard as a,
|
|
222
|
+
useUnsavedChangesGuard as b,
|
|
223
|
+
useReturnFocus as u
|
|
224
|
+
};
|
package/dist/server/i18n/de.json
CHANGED
|
@@ -20,7 +20,22 @@
|
|
|
20
20
|
"settings.updateFailed": "Einstellungen konnten nicht aktualisiert werden",
|
|
21
21
|
"settings.getFailed": "Einstellungen konnten nicht abgerufen werden",
|
|
22
22
|
"unauthorized": "Nicht autorisierter Zugriff",
|
|
23
|
-
"generic.error": "Ein Fehler ist aufgetreten"
|
|
23
|
+
"generic.error": "Ein Fehler ist aufgetreten",
|
|
24
|
+
"challenge.invalid": "Diese Bestätigungsanfrage ist ungültig oder abgelaufen",
|
|
25
|
+
"mfa.setupRequired": "Zwei-Faktor-Authentifizierung ist erforderlich. Bitte richten Sie zuerst eine Authenticator-App ein",
|
|
26
|
+
"mfa.invalidCode": "Ungültiger Bestätigungscode",
|
|
27
|
+
"otp.deliveryFailed": "Der Bestätigungscode konnte nicht gesendet werden. Bitte fordern Sie einen neuen Code an",
|
|
28
|
+
"totp.loginDisabled": "Die Anmeldung mit einem Authenticator-Code ist nicht aktiviert",
|
|
29
|
+
"credentials.invalid": "Ungültige Anmeldedaten",
|
|
30
|
+
"login.failed": "Die Anmeldung konnte nicht abgeschlossen werden. Bitte versuchen Sie es erneut",
|
|
31
|
+
"totp.notEnrolled": "Für dieses Konto ist keine Authenticator-App eingerichtet",
|
|
32
|
+
"totp.currentCodeRequired": "Geben Sie einen Code aus Ihrer aktuellen Authenticator-App ein, um fortzufahren",
|
|
33
|
+
"totp.noPendingSetup": "Es läuft keine Einrichtung einer Authenticator-App. Bitte starten Sie die Einrichtung erneut",
|
|
34
|
+
"totp.invalidEmailCode": "Der E-Mail-Code ist ungültig oder abgelaufen",
|
|
35
|
+
"totp.alreadyEnrolled": "Für dieses Konto ist bereits eine Authenticator-App eingerichtet",
|
|
36
|
+
"auth.required": "Bitte melden Sie sich an, um fortzufahren",
|
|
37
|
+
"settings.security.forbidden": "Für Änderungen an Link-Zielen, Vorlagen, Konto- oder MFA-Regeln ist die Berechtigung Einstellungen: Sicherheit erforderlich",
|
|
38
|
+
"settings.linkOrigin.notAllowed": "Anmeldelinks müssen einen erlaubten Origin verwenden"
|
|
24
39
|
},
|
|
25
40
|
"success": {
|
|
26
41
|
"token.created": "Magic Link erfolgreich gesendet",
|
|
@@ -32,5 +47,21 @@
|
|
|
32
47
|
"settings.updated": "Einstellungen erfolgreich aktualisiert",
|
|
33
48
|
"license.created": "Lizenz erfolgreich erstellt",
|
|
34
49
|
"license.verified": "Lizenz erfolgreich verifiziert"
|
|
50
|
+
},
|
|
51
|
+
"mail": {
|
|
52
|
+
"otp.subject": "Ihr Bestätigungscode",
|
|
53
|
+
"otp.heading": "Ihr Bestätigungscode",
|
|
54
|
+
"otp.intro": "Geben Sie diesen Code ein, um Ihre Anmeldung abzuschließen:",
|
|
55
|
+
"otp.expires": "Dieser Code läuft in {minutes} ab.",
|
|
56
|
+
"otp.ignore": "Wenn Sie diesen Code nicht angefordert haben, können Sie diese E-Mail ignorieren.",
|
|
57
|
+
"otp.preheader": "Ihr Bestätigungscode: {code}",
|
|
58
|
+
"setup.subject": "Bestätigen Sie Ihre Authenticator-App",
|
|
59
|
+
"setup.intro": "Geben Sie diesen Code ein, um die neue Authenticator-App für Ihr Konto zu bestätigen:",
|
|
60
|
+
"unit.minute": "{count} Minute",
|
|
61
|
+
"unit.minutes": "{count} Minuten",
|
|
62
|
+
"welcome.subject": "Willkommen",
|
|
63
|
+
"welcome.subjectApp": "Willkommen bei {app}",
|
|
64
|
+
"welcome.greeting": "Hallo {name},",
|
|
65
|
+
"welcome.body": "Danke für Ihre Registrierung. Ihr Anmeldelink ist unterwegs."
|
|
35
66
|
}
|
|
36
67
|
}
|
package/dist/server/i18n/en.json
CHANGED
|
@@ -20,7 +20,22 @@
|
|
|
20
20
|
"settings.updateFailed": "Could not update settings",
|
|
21
21
|
"settings.getFailed": "Could not get settings",
|
|
22
22
|
"unauthorized": "Unauthorized access",
|
|
23
|
-
"generic.error": "An error occurred"
|
|
23
|
+
"generic.error": "An error occurred",
|
|
24
|
+
"challenge.invalid": "This verification request is invalid or has expired",
|
|
25
|
+
"mfa.setupRequired": "Two-factor authentication is required. Please set up an authenticator app first",
|
|
26
|
+
"mfa.invalidCode": "Invalid verification code",
|
|
27
|
+
"otp.deliveryFailed": "The verification code could not be sent. Please request a new code",
|
|
28
|
+
"totp.loginDisabled": "Login with an authenticator code is not enabled",
|
|
29
|
+
"credentials.invalid": "Invalid credentials",
|
|
30
|
+
"login.failed": "The login could not be completed. Please try again",
|
|
31
|
+
"totp.notEnrolled": "No authenticator app is set up for this account",
|
|
32
|
+
"totp.currentCodeRequired": "Enter a code from your current authenticator app to continue",
|
|
33
|
+
"totp.noPendingSetup": "No authenticator setup is in progress. Please start the setup again",
|
|
34
|
+
"totp.invalidEmailCode": "The e-mail code is invalid or has expired",
|
|
35
|
+
"totp.alreadyEnrolled": "An authenticator app is already set up for this account",
|
|
36
|
+
"auth.required": "Please log in to continue",
|
|
37
|
+
"settings.security.forbidden": "Changing link targets, templates, account or MFA rules requires the Settings: Security permission",
|
|
38
|
+
"settings.linkOrigin.notAllowed": "Login link URLs must use an allowed origin"
|
|
24
39
|
},
|
|
25
40
|
"success": {
|
|
26
41
|
"token.created": "Magic Link sent successfully",
|
|
@@ -32,5 +47,21 @@
|
|
|
32
47
|
"settings.updated": "Settings updated successfully",
|
|
33
48
|
"license.created": "License created successfully",
|
|
34
49
|
"license.verified": "License verified successfully"
|
|
50
|
+
},
|
|
51
|
+
"mail": {
|
|
52
|
+
"otp.subject": "Your verification code",
|
|
53
|
+
"otp.heading": "Your verification code",
|
|
54
|
+
"otp.intro": "Enter this code to complete your login:",
|
|
55
|
+
"otp.expires": "This code will expire in {minutes}.",
|
|
56
|
+
"otp.ignore": "If you didn't request this code, you can safely ignore this email.",
|
|
57
|
+
"otp.preheader": "Your verification code: {code}",
|
|
58
|
+
"setup.subject": "Confirm your authenticator app",
|
|
59
|
+
"setup.intro": "Enter this code to confirm the new authenticator app for your account:",
|
|
60
|
+
"unit.minute": "{count} minute",
|
|
61
|
+
"unit.minutes": "{count} minutes",
|
|
62
|
+
"welcome.subject": "Welcome",
|
|
63
|
+
"welcome.subjectApp": "Welcome to {app}",
|
|
64
|
+
"welcome.greeting": "Hi {name},",
|
|
65
|
+
"welcome.body": "Thanks for joining. Your login link is on the way."
|
|
35
66
|
}
|
|
36
67
|
}
|
package/dist/server/i18n/es.json
CHANGED
|
@@ -20,7 +20,22 @@
|
|
|
20
20
|
"settings.updateFailed": "No se pudieron actualizar las configuraciones",
|
|
21
21
|
"settings.getFailed": "No se pudieron obtener las configuraciones",
|
|
22
22
|
"unauthorized": "Acceso no autorizado",
|
|
23
|
-
"generic.error": "Se ha producido un error"
|
|
23
|
+
"generic.error": "Se ha producido un error",
|
|
24
|
+
"challenge.invalid": "Esta solicitud de verificación no es válida o ha caducado",
|
|
25
|
+
"mfa.setupRequired": "Se requiere autenticación de dos factores. Configure primero una aplicación de autenticación",
|
|
26
|
+
"mfa.invalidCode": "Código de verificación no válido",
|
|
27
|
+
"otp.deliveryFailed": "No se pudo enviar el código de verificación. Solicite un código nuevo",
|
|
28
|
+
"totp.loginDisabled": "El inicio de sesión con un código de autenticación no está activado",
|
|
29
|
+
"credentials.invalid": "Credenciales no válidas",
|
|
30
|
+
"login.failed": "No se pudo completar el inicio de sesión. Inténtelo de nuevo",
|
|
31
|
+
"totp.notEnrolled": "No hay ninguna aplicación de autenticación configurada para esta cuenta",
|
|
32
|
+
"totp.currentCodeRequired": "Introduzca un código de su aplicación de autenticación actual para continuar",
|
|
33
|
+
"totp.noPendingSetup": "No hay ninguna configuración de aplicación de autenticación en curso. Inicie la configuración de nuevo",
|
|
34
|
+
"totp.invalidEmailCode": "El código del correo electrónico no es válido o ha caducado",
|
|
35
|
+
"totp.alreadyEnrolled": "Ya hay una aplicación de autenticación configurada para esta cuenta",
|
|
36
|
+
"auth.required": "Inicie sesión para continuar",
|
|
37
|
+
"settings.security.forbidden": "Cambiar destinos de enlaces, plantillas o reglas de cuenta o MFA requiere el permiso Configuración: Seguridad",
|
|
38
|
+
"settings.linkOrigin.notAllowed": "Las URL de los enlaces de inicio de sesión deben usar un origen permitido"
|
|
24
39
|
},
|
|
25
40
|
"success": {
|
|
26
41
|
"token.created": "Magic Link enviado exitosamente",
|
|
@@ -32,5 +47,21 @@
|
|
|
32
47
|
"settings.updated": "Configuraciones actualizadas exitosamente",
|
|
33
48
|
"license.created": "Licencia creada exitosamente",
|
|
34
49
|
"license.verified": "Licencia verificada exitosamente"
|
|
50
|
+
},
|
|
51
|
+
"mail": {
|
|
52
|
+
"otp.subject": "Tu código de verificación",
|
|
53
|
+
"otp.heading": "Tu código de verificación",
|
|
54
|
+
"otp.intro": "Introduce este código para completar el inicio de sesión:",
|
|
55
|
+
"otp.expires": "Este código caduca en {minutes}.",
|
|
56
|
+
"otp.ignore": "Si no has solicitado este código, puedes ignorar este correo.",
|
|
57
|
+
"otp.preheader": "Tu código de verificación: {code}",
|
|
58
|
+
"setup.subject": "Confirma tu aplicación de autenticación",
|
|
59
|
+
"setup.intro": "Introduce este código para confirmar la nueva aplicación de autenticación de tu cuenta:",
|
|
60
|
+
"unit.minute": "{count} minuto",
|
|
61
|
+
"unit.minutes": "{count} minutos",
|
|
62
|
+
"welcome.subject": "Bienvenido",
|
|
63
|
+
"welcome.subjectApp": "Bienvenido a {app}",
|
|
64
|
+
"welcome.greeting": "Hola {name}:",
|
|
65
|
+
"welcome.body": "Gracias por registrarte. Tu enlace de inicio de sesión está en camino."
|
|
35
66
|
}
|
|
36
67
|
}
|
package/dist/server/i18n/fr.json
CHANGED
|
@@ -20,7 +20,22 @@
|
|
|
20
20
|
"settings.updateFailed": "Impossible de mettre à jour les paramètres",
|
|
21
21
|
"settings.getFailed": "Impossible de récupérer les paramètres",
|
|
22
22
|
"unauthorized": "Accès non autorisé",
|
|
23
|
-
"generic.error": "Une erreur s'est produite"
|
|
23
|
+
"generic.error": "Une erreur s'est produite",
|
|
24
|
+
"challenge.invalid": "Cette demande de vérification est invalide ou a expiré",
|
|
25
|
+
"mfa.setupRequired": "L'authentification à deux facteurs est requise. Veuillez d'abord configurer une application d'authentification",
|
|
26
|
+
"mfa.invalidCode": "Code de vérification invalide",
|
|
27
|
+
"otp.deliveryFailed": "Le code de vérification n'a pas pu être envoyé. Veuillez demander un nouveau code",
|
|
28
|
+
"totp.loginDisabled": "La connexion avec un code d'authentification n'est pas activée",
|
|
29
|
+
"credentials.invalid": "Identifiants invalides",
|
|
30
|
+
"login.failed": "La connexion n'a pas pu être finalisée. Veuillez réessayer",
|
|
31
|
+
"totp.notEnrolled": "Aucune application d'authentification n'est configurée pour ce compte",
|
|
32
|
+
"totp.currentCodeRequired": "Saisissez un code de votre application d'authentification actuelle pour continuer",
|
|
33
|
+
"totp.noPendingSetup": "Aucune configuration d'application d'authentification n'est en cours. Veuillez recommencer la configuration",
|
|
34
|
+
"totp.invalidEmailCode": "Le code reçu par e-mail est invalide ou a expiré",
|
|
35
|
+
"totp.alreadyEnrolled": "Une application d'authentification est déjà configurée pour ce compte",
|
|
36
|
+
"auth.required": "Veuillez vous connecter pour continuer",
|
|
37
|
+
"settings.security.forbidden": "Modifier les cibles des liens, les modèles ou les règles de compte ou MFA nécessite l'autorisation Paramètres : Sécurité",
|
|
38
|
+
"settings.linkOrigin.notAllowed": "Les URL des liens de connexion doivent utiliser une origine autorisée"
|
|
24
39
|
},
|
|
25
40
|
"success": {
|
|
26
41
|
"token.created": "Magic Link envoyé avec succès",
|
|
@@ -32,5 +47,21 @@
|
|
|
32
47
|
"settings.updated": "Paramètres mis à jour avec succès",
|
|
33
48
|
"license.created": "Licence créée avec succès",
|
|
34
49
|
"license.verified": "Licence vérifiée avec succès"
|
|
50
|
+
},
|
|
51
|
+
"mail": {
|
|
52
|
+
"otp.subject": "Votre code de vérification",
|
|
53
|
+
"otp.heading": "Votre code de vérification",
|
|
54
|
+
"otp.intro": "Saisissez ce code pour terminer votre connexion :",
|
|
55
|
+
"otp.expires": "Ce code expire dans {minutes}.",
|
|
56
|
+
"otp.ignore": "Si vous n'avez pas demandé ce code, vous pouvez ignorer cet e-mail.",
|
|
57
|
+
"otp.preheader": "Votre code de vérification : {code}",
|
|
58
|
+
"setup.subject": "Confirmez votre application d'authentification",
|
|
59
|
+
"setup.intro": "Saisissez ce code pour confirmer la nouvelle application d'authentification de votre compte :",
|
|
60
|
+
"unit.minute": "{count} minute",
|
|
61
|
+
"unit.minutes": "{count} minutes",
|
|
62
|
+
"welcome.subject": "Bienvenue",
|
|
63
|
+
"welcome.subjectApp": "Bienvenue sur {app}",
|
|
64
|
+
"welcome.greeting": "Bonjour {name},",
|
|
65
|
+
"welcome.body": "Merci de votre inscription. Votre lien de connexion arrive."
|
|
35
66
|
}
|
|
36
67
|
}
|