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.
Files changed (58) hide show
  1. package/CHANGELOG.md +733 -0
  2. package/README.md +662 -928
  3. package/dist/admin/LicenseGuard-BgLA9go3.mjs +495 -0
  4. package/dist/admin/LicenseGuard-ewCjpc17.js +513 -0
  5. package/dist/admin/de-B1FNErPH.mjs +673 -0
  6. package/dist/admin/de-e75OECdq.js +673 -0
  7. package/dist/admin/en-CABKGbxH.js +673 -0
  8. package/dist/admin/en-HqDOFBCc.mjs +673 -0
  9. package/dist/admin/es-DpWl0Fax.js +673 -0
  10. package/dist/admin/es-jb94jqPR.mjs +673 -0
  11. package/dist/admin/fr-BQhLPvYw.js +673 -0
  12. package/dist/admin/fr-BseH1wrr.mjs +673 -0
  13. package/dist/admin/index-6ot9j1cN.js +5268 -0
  14. package/dist/admin/index-B390rccl.js +622 -0
  15. package/dist/admin/index-BPGKtruf.mjs +5266 -0
  16. package/dist/admin/index-BaTIQtnA.js +3869 -0
  17. package/dist/admin/index-CmG-93yx.js +141 -0
  18. package/dist/admin/index-DXe_ge4v.mjs +620 -0
  19. package/dist/admin/index-Dv5PVT1E.mjs +3867 -0
  20. package/dist/admin/index-k240ls6p.mjs +142 -0
  21. package/dist/admin/index.js +3 -3
  22. package/dist/admin/index.mjs +1 -2
  23. package/dist/admin/pt-CPkaveti.js +673 -0
  24. package/dist/admin/pt-DhlSCJn2.mjs +673 -0
  25. package/dist/admin/useUnsavedChangesGuard-BVZ4Mg4E.js +225 -0
  26. package/dist/admin/useUnsavedChangesGuard-QnkiZTLP.mjs +224 -0
  27. package/dist/server/i18n/de.json +32 -1
  28. package/dist/server/i18n/en.json +32 -1
  29. package/dist/server/i18n/es.json +32 -1
  30. package/dist/server/i18n/fr.json +32 -1
  31. package/dist/server/i18n/pt.json +32 -1
  32. package/dist/server/index.js +7092 -5971
  33. package/dist/server/index.mjs +7115 -5972
  34. package/package.json +42 -41
  35. package/strapi-admin.js +3 -1
  36. package/strapi-server.js +3 -2
  37. package/dist/_chunks/LicenseGuard-CiyjFnrw.js +0 -54
  38. package/dist/_chunks/LicenseGuard-DJ5fq2UZ.mjs +0 -55
  39. package/dist/_chunks/de-BSOk5suQ.js +0 -454
  40. package/dist/_chunks/de-CwhYs3O3.mjs +0 -454
  41. package/dist/_chunks/en-DnvSpXvu.js +0 -454
  42. package/dist/_chunks/en-gXXgGDV1.mjs +0 -454
  43. package/dist/_chunks/es-B-tnwByt.js +0 -407
  44. package/dist/_chunks/es-DG5gW3RZ.mjs +0 -407
  45. package/dist/_chunks/fr-BKo8t7Fx.js +0 -407
  46. package/dist/_chunks/fr-BQn1OHOR.mjs +0 -407
  47. package/dist/_chunks/index-01GHNxSr.js +0 -307
  48. package/dist/_chunks/index-5jsDgD-q.mjs +0 -330
  49. package/dist/_chunks/index-BWuj-5WE.js +0 -5068
  50. package/dist/_chunks/index-Bj3H_Z4B.js +0 -142
  51. package/dist/_chunks/index-CC4Eu9Dm.js +0 -332
  52. package/dist/_chunks/index-Csyi13vd.mjs +0 -2891
  53. package/dist/_chunks/index-DJqXiiP1.js +0 -2893
  54. package/dist/_chunks/index-DQUYFsLT.mjs +0 -143
  55. package/dist/_chunks/index-Dufv-daf.mjs +0 -305
  56. package/dist/_chunks/index-szeYJKpN.mjs +0 -5066
  57. package/dist/_chunks/pt-CjEkXBAz.js +0 -407
  58. package/dist/_chunks/pt-trziAOzT.mjs +0 -407
@@ -0,0 +1,620 @@
1
+ import { jsxs, jsx, Fragment } from "react/jsx-runtime";
2
+ import { useState, useRef, useCallback, useEffect } from "react";
3
+ import { Alert, VisuallyHidden, Flex, Badge, Accordion, Box, Typography, Button, Loader } from "@strapi/design-system";
4
+ import { useFetchClient, useNotification, Page } from "@strapi/strapi/admin";
5
+ import { EyeStriked, Eye, Duplicate, Download, User, Shield, ChartBubble, ArrowClockwise } from "@strapi/icons";
6
+ import styled, { css, keyframes } from "styled-components";
7
+ import { u as useMagicLinkIntl, h as useFormatters, j as useCopyToClipboard, i as useMagicLinkRBAC, P as PluginPageTitle, L as LicenseGuard, k as LanguageProvider, s as space, a as brand, t as theme, b as breakpoints, r as reducedMotion, m as motionSafe, f as fontSize, c as color } from "./LicenseGuard-BgLA9go3.mjs";
8
+ const MASK_CHAR = "•";
9
+ const isMaskedKey = (value) => typeof value === "string" && (value.includes(MASK_CHAR) || value.includes("*"));
10
+ const maskLicenseKey = (key) => {
11
+ if (typeof key !== "string" || !key.trim()) return "";
12
+ const trimmed = key.trim();
13
+ if (isMaskedKey(trimmed)) return trimmed;
14
+ const hidden = MASK_CHAR.repeat(4);
15
+ if (trimmed.length < 12) return `${hidden}${hidden}`;
16
+ return `${hidden}-${hidden}-${trimmed.slice(-4)}`;
17
+ };
18
+ const getLicenseView = (payload) => {
19
+ const details = payload?.data && typeof payload.data === "object" ? payload.data : {};
20
+ const rawKey = typeof details.licenseKey === "string" ? details.licenseKey.trim() : "";
21
+ const fullKey = rawKey && !isMaskedKey(rawKey) ? rawKey : null;
22
+ const maskedKey = typeof details.licenseKeyMasked === "string" && details.licenseKeyMasked.trim() || maskLicenseKey(rawKey);
23
+ const hasKey = Boolean(payload?.hasKey);
24
+ const isValid = payload?.valid !== false;
25
+ let kind = "none";
26
+ if (hasKey) kind = isValid ? "active" : "unverified";
27
+ return {
28
+ kind,
29
+ hasKey,
30
+ fullKey,
31
+ maskedKey,
32
+ isActive: details.isActive !== false,
33
+ details
34
+ };
35
+ };
36
+ const holderName = (details = {}) => [details.firstName, details.lastName].filter((part) => typeof part === "string" && part.trim()).map((part) => part.trim()).join(" ");
37
+ const buildLicenseFileContent = (values, labels) => {
38
+ const rule = "=".repeat(40);
39
+ const line = "-".repeat(40);
40
+ return [
41
+ labels.title,
42
+ rule,
43
+ "",
44
+ `${labels.key}: ${values.key}`,
45
+ "",
46
+ labels.holder,
47
+ line,
48
+ `${labels.name}: ${values.name}`,
49
+ `${labels.email}: ${values.email}`,
50
+ "",
51
+ labels.statusSection,
52
+ line,
53
+ `${labels.status}: ${values.status}`,
54
+ `${labels.expires}: ${values.expires}`,
55
+ "",
56
+ rule,
57
+ `${labels.generated}: ${values.generated}`,
58
+ ""
59
+ ].join("\n");
60
+ };
61
+ const LICENSE_FILE_NAME = "magic-link-license.txt";
62
+ const licenseErrorMessage = (error, t) => {
63
+ const status = error?.status ?? error?.response?.status;
64
+ if (status === 401 || status === 403) {
65
+ return t(
66
+ "license.error.forbidden",
67
+ "You do not have permission to view the license. Ask an administrator for the Settings read permission."
68
+ );
69
+ }
70
+ return t("license.error.load", "The license information could not be loaded. Try again.");
71
+ };
72
+ const fadeIn = keyframes`
73
+ from { opacity: 0; transform: translateY(8px); }
74
+ to { opacity: 1; transform: translateY(0); }
75
+ `;
76
+ const shimmer = keyframes`
77
+ 0% { background-position: -200% 0; }
78
+ 100% { background-position: 200% 0; }
79
+ `;
80
+ const Container = styled(Box)`
81
+ max-width: 1440px;
82
+ margin: 0 auto;
83
+ ${motionSafe(css`
84
+ animation: ${fadeIn} 0.4s ease-out;
85
+ `)}
86
+ `;
87
+ const StickyHeader = styled(Box)`
88
+ position: sticky;
89
+ top: 0;
90
+ z-index: 10;
91
+ background: ${color("neutral0")};
92
+ border-bottom: 1px solid ${color("neutral200")};
93
+ box-shadow: ${theme.shadows.sm};
94
+ padding: ${space(6)};
95
+
96
+ @media (max-width: ${breakpoints.mobile}) {
97
+ padding: ${space(4)};
98
+ }
99
+ `;
100
+ const HeaderRow = styled(Flex)`
101
+ flex-wrap: wrap;
102
+ gap: ${space(4)};
103
+ justify-content: space-between;
104
+ align-items: center;
105
+
106
+ & > :first-child {
107
+ min-width: 0;
108
+ flex: 1 1 240px;
109
+ }
110
+ `;
111
+ const GradientButton = styled(Button)`
112
+ background: ${brand.gradient};
113
+ border: none;
114
+ color: ${brand.onFill};
115
+
116
+ & > span,
117
+ & svg {
118
+ color: ${brand.onFill};
119
+ fill: ${brand.onFill};
120
+ }
121
+
122
+ &:hover:not([aria-disabled='true']) {
123
+ background: ${brand.gradientHover};
124
+ }
125
+ `;
126
+ const Content = styled(Box)`
127
+ padding: ${space(6)};
128
+
129
+ @media (max-width: ${breakpoints.mobile}) {
130
+ padding: ${space(4)};
131
+ }
132
+ `;
133
+ const Stack = styled(Flex)`
134
+ flex-direction: column;
135
+ align-items: stretch;
136
+ gap: ${space(6)};
137
+ `;
138
+ const LicenseKeyBanner = styled.section`
139
+ background:
140
+ linear-gradient(rgba(15, 23, 42, 0.25), rgba(15, 23, 42, 0.25)), ${brand.violetGradient};
141
+ border-radius: ${theme.borderRadius.lg};
142
+ padding: ${space(6)} ${space(7)};
143
+ color: ${brand.onFill};
144
+ position: relative;
145
+ overflow: hidden;
146
+ box-shadow: 0 4px 20px rgba(102, 126, 234, 0.25);
147
+
148
+ @media (max-width: ${breakpoints.mobile}) {
149
+ padding: ${space(4)};
150
+ }
151
+
152
+ &::after {
153
+ content: '';
154
+ position: absolute;
155
+ top: -50%;
156
+ right: -50%;
157
+ width: 200%;
158
+ height: 200%;
159
+ background: linear-gradient(45deg, transparent, rgba(255, 255, 255, 0.08), transparent);
160
+ background-size: 200% 100%;
161
+ ${motionSafe(css`
162
+ animation: ${shimmer} 3s infinite;
163
+ `)}
164
+ pointer-events: none;
165
+ z-index: 0;
166
+ }
167
+
168
+ & > * {
169
+ position: relative;
170
+ z-index: 1;
171
+ }
172
+
173
+ ${reducedMotion}
174
+ `;
175
+ const BannerRow = styled.div`
176
+ display: flex;
177
+ flex-wrap: wrap;
178
+ gap: ${space(4)};
179
+ justify-content: space-between;
180
+ align-items: flex-start;
181
+ `;
182
+ const BannerBody = styled.div`
183
+ flex: 1 1 260px;
184
+ min-width: 0;
185
+ display: flex;
186
+ flex-direction: column;
187
+ gap: ${space(3)};
188
+ `;
189
+ const BannerLabel = styled.h2`
190
+ margin: 0;
191
+ color: ${brand.onFill};
192
+ font-size: ${fontSize(0)};
193
+ font-weight: 600;
194
+ letter-spacing: 0.5px;
195
+ text-transform: uppercase;
196
+ `;
197
+ const KeyText = styled.p`
198
+ margin: 0;
199
+ color: ${brand.onFill};
200
+ font-family: monospace;
201
+ font-size: ${fontSize(6)};
202
+ font-weight: 700;
203
+ line-height: 1.25;
204
+ overflow-wrap: anywhere;
205
+
206
+ @media (max-width: ${breakpoints.mobile}) {
207
+ font-size: ${fontSize(4)};
208
+ }
209
+ `;
210
+ const FrostedButton = styled(Button)`
211
+ background: rgba(15, 23, 42, 0.2);
212
+ border: 1px solid rgba(255, 255, 255, 0.5);
213
+ color: ${brand.onFill};
214
+
215
+ & > span,
216
+ & svg {
217
+ color: ${brand.onFill};
218
+ fill: ${brand.onFill};
219
+ }
220
+
221
+ &:hover:not([aria-disabled='true']) {
222
+ background: rgba(15, 23, 42, 0.32);
223
+ border-color: ${brand.onFill};
224
+ }
225
+
226
+ &:focus-visible {
227
+ outline: 2px solid ${brand.onFill};
228
+ outline-offset: 2px;
229
+ }
230
+ `;
231
+ const DetailGrid = styled.dl`
232
+ display: grid;
233
+ grid-template-columns: repeat(auto-fit, minmax(min(180px, 100%), 1fr));
234
+ align-items: stretch;
235
+ gap: ${space(6)};
236
+ margin: 0;
237
+ `;
238
+ const DetailItem = styled.div`
239
+ display: flex;
240
+ flex-direction: column;
241
+ gap: ${space(2)};
242
+ min-width: 0;
243
+
244
+ & > dd {
245
+ margin: 0;
246
+ overflow-wrap: anywhere;
247
+ }
248
+ `;
249
+ const LoaderArea = styled(Flex)`
250
+ min-height: 320px;
251
+ align-items: center;
252
+ justify-content: center;
253
+ `;
254
+ const Detail = ({ label, children }) => /* @__PURE__ */ jsxs(DetailItem, { children: [
255
+ /* @__PURE__ */ jsx(Typography, { tag: "dt", variant: "sigma", textColor: "neutral600", children: label }),
256
+ /* @__PURE__ */ jsx(Typography, { tag: "dd", variant: "omega", fontWeight: "semiBold", textColor: "neutral800", children })
257
+ ] });
258
+ const describeLicense = (view, t, formatDate) => {
259
+ const notProvided = t("license.value.notProvided", "Not provided");
260
+ return {
261
+ name: holderName(view.details),
262
+ notProvided,
263
+ expiresText: view.details.expiresAt ? formatDate(view.details.expiresAt, { dateStyle: "long" }, notProvided) : t("license.value.never", "Never"),
264
+ activeLabel: view.isActive ? t("license.status.active", "Active") : t("license.status.inactive", "Inactive")
265
+ };
266
+ };
267
+ const LicenseOverview = ({
268
+ view,
269
+ canUseFullKey,
270
+ revealed,
271
+ alertDismissed,
272
+ onDismissAlert,
273
+ onToggleReveal,
274
+ onCopy,
275
+ onDownload
276
+ }) => {
277
+ const { t } = useMagicLinkIntl();
278
+ const { formatDate } = useFormatters();
279
+ const { details } = view;
280
+ const { name, notProvided, expiresText, activeLabel } = describeLicense(view, t, formatDate);
281
+ const closeLabel = t("license.alert.close", "Dismiss message");
282
+ const isRevealed = revealed && canUseFullKey;
283
+ const alertProps = {
284
+ active: {
285
+ variant: "success",
286
+ title: t("license.alert.active.title", "License active"),
287
+ body: t(
288
+ "license.alert.active.body",
289
+ "License key registered. Thank you for supporting the plugin."
290
+ )
291
+ },
292
+ unverified: {
293
+ variant: "warning",
294
+ title: t("license.alert.unverified.title", "License not verified"),
295
+ body: t(
296
+ "license.alert.unverified.body",
297
+ "The license server could not be reached. The plugin keeps working. Refresh the status later."
298
+ )
299
+ },
300
+ none: {
301
+ variant: "default",
302
+ title: t("license.alert.none.title", "No license key registered"),
303
+ body: t(
304
+ "license.alert.none.body",
305
+ "All Magic Link features are available without activation."
306
+ )
307
+ }
308
+ }[view.kind];
309
+ return /* @__PURE__ */ jsxs(Stack, { children: [
310
+ !alertDismissed && /* @__PURE__ */ jsx(
311
+ Alert,
312
+ {
313
+ variant: alertProps.variant,
314
+ title: alertProps.title,
315
+ closeLabel,
316
+ onClose: onDismissAlert,
317
+ children: alertProps.body
318
+ }
319
+ ),
320
+ view.maskedKey && /* @__PURE__ */ jsx(LicenseKeyBanner, { "aria-labelledby": "magic-link-license-key-label", children: /* @__PURE__ */ jsxs(BannerRow, { children: [
321
+ /* @__PURE__ */ jsxs(BannerBody, { children: [
322
+ /* @__PURE__ */ jsx(BannerLabel, { id: "magic-link-license-key-label", children: t("license.key.label", "License key") }),
323
+ isRevealed ? /* @__PURE__ */ jsx(KeyText, { children: view.fullKey }) : /* @__PURE__ */ jsxs(KeyText, { children: [
324
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: view.maskedKey }),
325
+ /* @__PURE__ */ jsx(VisuallyHidden, { children: t("license.key.hidden", "License key hidden") })
326
+ ] }),
327
+ canUseFullKey && /* @__PURE__ */ jsxs(Flex, { gap: 2, wrap: "wrap", children: [
328
+ /* @__PURE__ */ jsx(
329
+ FrostedButton,
330
+ {
331
+ size: "M",
332
+ variant: "secondary",
333
+ onClick: onToggleReveal,
334
+ "aria-pressed": isRevealed,
335
+ startIcon: isRevealed ? /* @__PURE__ */ jsx(EyeStriked, { "aria-hidden": "true" }) : /* @__PURE__ */ jsx(Eye, { "aria-hidden": "true" }),
336
+ children: isRevealed ? t("license.key.hide", "Hide key") : t("license.key.show", "Show key")
337
+ }
338
+ ),
339
+ /* @__PURE__ */ jsx(
340
+ FrostedButton,
341
+ {
342
+ size: "M",
343
+ variant: "secondary",
344
+ onClick: onCopy,
345
+ startIcon: /* @__PURE__ */ jsx(Duplicate, { "aria-hidden": "true" }),
346
+ children: t("license.key.copy", "Copy key")
347
+ }
348
+ ),
349
+ /* @__PURE__ */ jsx(
350
+ FrostedButton,
351
+ {
352
+ size: "M",
353
+ variant: "secondary",
354
+ onClick: onDownload,
355
+ startIcon: /* @__PURE__ */ jsx(Download, { "aria-hidden": "true" }),
356
+ children: t("license.key.download", "Download as TXT")
357
+ }
358
+ )
359
+ ] })
360
+ ] }),
361
+ /* @__PURE__ */ jsx(
362
+ Badge,
363
+ {
364
+ size: "M",
365
+ backgroundColor: view.isActive ? "success100" : "danger100",
366
+ textColor: view.isActive ? "success700" : "danger700",
367
+ children: activeLabel
368
+ }
369
+ )
370
+ ] }) }),
371
+ /* @__PURE__ */ jsxs(Accordion.Root, { defaultValue: "account", children: [
372
+ /* @__PURE__ */ jsxs(Accordion.Item, { value: "account", children: [
373
+ /* @__PURE__ */ jsx(Accordion.Header, { children: /* @__PURE__ */ jsx(Accordion.Trigger, { icon: User, children: t("license.section.account", "Account information") }) }),
374
+ /* @__PURE__ */ jsx(Accordion.Content, { children: /* @__PURE__ */ jsx(Box, { padding: 6, children: /* @__PURE__ */ jsxs(DetailGrid, { children: [
375
+ /* @__PURE__ */ jsx(Detail, { label: t("license.field.email", "Email address"), children: details.email || notProvided }),
376
+ /* @__PURE__ */ jsx(Detail, { label: t("license.field.holder", "Name"), children: name || notProvided })
377
+ ] }) }) })
378
+ ] }),
379
+ /* @__PURE__ */ jsxs(Accordion.Item, { value: "details", children: [
380
+ /* @__PURE__ */ jsx(Accordion.Header, { children: /* @__PURE__ */ jsx(Accordion.Trigger, { icon: Shield, children: t("license.section.details", "License details") }) }),
381
+ /* @__PURE__ */ jsx(Accordion.Content, { children: /* @__PURE__ */ jsx(Box, { padding: 6, children: /* @__PURE__ */ jsxs(DetailGrid, { children: [
382
+ /* @__PURE__ */ jsx(
383
+ Detail,
384
+ {
385
+ label: details.isExpired ? t("license.field.expired", "Expired on") : t("license.field.expires", "Expires on"),
386
+ children: expiresText
387
+ }
388
+ ),
389
+ /* @__PURE__ */ jsx(Detail, { label: t("license.field.deviceName", "Device name"), children: details.deviceName || notProvided }),
390
+ /* @__PURE__ */ jsx(Detail, { label: t("license.field.ipAddress", "IP address"), children: details.ipAddress || notProvided })
391
+ ] }) }) })
392
+ ] }),
393
+ /* @__PURE__ */ jsxs(Accordion.Item, { value: "status", children: [
394
+ /* @__PURE__ */ jsx(Accordion.Header, { children: /* @__PURE__ */ jsx(Accordion.Trigger, { icon: ChartBubble, children: t("license.section.status", "System status") }) }),
395
+ /* @__PURE__ */ jsx(Accordion.Content, { children: /* @__PURE__ */ jsx(Box, { padding: 6, children: /* @__PURE__ */ jsxs(DetailGrid, { children: [
396
+ /* @__PURE__ */ jsx(Detail, { label: t("license.field.status", "Status"), children: view.hasKey ? activeLabel : t("license.status.noKey", "No key registered") }),
397
+ /* @__PURE__ */ jsx(Detail, { label: t("license.field.mode", "Plugin mode"), children: t("license.mode.unlocked", "All features available") })
398
+ ] }) }) })
399
+ ] })
400
+ ] })
401
+ ] });
402
+ };
403
+ const LicenseView = () => {
404
+ const { get } = useFetchClient();
405
+ const { toggleNotification } = useNotification();
406
+ const { t } = useMagicLinkIntl();
407
+ const { formatDate, formatDateTime } = useFormatters();
408
+ const copy = useCopyToClipboard();
409
+ const {
410
+ canReadSettings,
411
+ canUpdateSettings,
412
+ isLoading: rbacLoading,
413
+ error: rbacError
414
+ } = useMagicLinkRBAC();
415
+ const [status, setStatus] = useState("loading");
416
+ const [licenseData, setLicenseData] = useState(null);
417
+ const [loadError, setLoadError] = useState(null);
418
+ const [isRefreshing, setIsRefreshing] = useState(true);
419
+ const [revealed, setRevealed] = useState(false);
420
+ const [alertDismissed, setAlertDismissed] = useState(false);
421
+ const requestRef = useRef(null);
422
+ const loadLicenseStatus = useCallback(async () => {
423
+ requestRef.current?.abort();
424
+ const controller = new AbortController();
425
+ requestRef.current = controller;
426
+ try {
427
+ const response = await get("/magic-link/license/status", { signal: controller.signal });
428
+ if (controller.signal.aborted) return;
429
+ setLicenseData(response.data);
430
+ setLoadError(null);
431
+ setStatus("ready");
432
+ } catch (err) {
433
+ if (controller.signal.aborted) return;
434
+ setLoadError({ status: err?.status ?? err?.response?.status });
435
+ setStatus("error");
436
+ } finally {
437
+ if (requestRef.current === controller) {
438
+ requestRef.current = null;
439
+ setIsRefreshing(false);
440
+ }
441
+ }
442
+ }, [get]);
443
+ const refreshLicenseStatus = useCallback(() => {
444
+ setIsRefreshing(true);
445
+ setRevealed(false);
446
+ setAlertDismissed(false);
447
+ return loadLicenseStatus();
448
+ }, [loadLicenseStatus]);
449
+ useEffect(() => {
450
+ if (!canReadSettings) return void 0;
451
+ const load = async () => {
452
+ await loadLicenseStatus();
453
+ };
454
+ load();
455
+ return () => requestRef.current?.abort();
456
+ }, [canReadSettings, loadLicenseStatus]);
457
+ const pageTitle = /* @__PURE__ */ jsx(PluginPageTitle, { children: t("license.pageTitle", "License") });
458
+ if (rbacLoading) {
459
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
460
+ pageTitle,
461
+ /* @__PURE__ */ jsx(Page.Loading, { children: t("common.loading", "Loading...") })
462
+ ] });
463
+ }
464
+ if (rbacError) {
465
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
466
+ pageTitle,
467
+ /* @__PURE__ */ jsx(
468
+ Page.Error,
469
+ {
470
+ content: t(
471
+ "common.permissions.loadError",
472
+ "Your permissions could not be loaded. Reload the page to try again."
473
+ )
474
+ }
475
+ )
476
+ ] });
477
+ }
478
+ if (!canReadSettings) {
479
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
480
+ pageTitle,
481
+ /* @__PURE__ */ jsx(Page.NoPermissions, {})
482
+ ] });
483
+ }
484
+ const view = getLicenseView(licenseData);
485
+ const canUseFullKey = canUpdateSettings && Boolean(view.fullKey);
486
+ const handleCopyLicenseKey = async () => {
487
+ if (!canUseFullKey) return;
488
+ await copy(view.fullKey, { success: t("license.copy.success", "License key copied") });
489
+ };
490
+ const handleDownloadLicenseKey = () => {
491
+ if (!canUseFullKey) return;
492
+ const { name, notProvided, expiresText, activeLabel } = describeLicense(view, t, formatDate);
493
+ let url = null;
494
+ try {
495
+ const content = buildLicenseFileContent(
496
+ {
497
+ key: view.fullKey,
498
+ name: name || notProvided,
499
+ email: view.details.email || notProvided,
500
+ status: activeLabel,
501
+ expires: expiresText,
502
+ generated: formatDateTime(/* @__PURE__ */ new Date())
503
+ },
504
+ {
505
+ title: t("license.file.title", "Magic Link plugin license key"),
506
+ key: t("license.key.label", "License key"),
507
+ holder: t("license.file.holder", "License holder"),
508
+ name: t("license.field.holder", "Name"),
509
+ email: t("license.field.email", "Email address"),
510
+ statusSection: t("license.file.statusSection", "License status"),
511
+ status: t("license.field.status", "Status"),
512
+ expires: t("license.field.expires", "Expires on"),
513
+ generated: t("license.file.generated", "Generated")
514
+ }
515
+ );
516
+ const blob = new Blob([content], { type: "text/plain;charset=utf-8" });
517
+ url = window.URL.createObjectURL(blob);
518
+ const link = document.createElement("a");
519
+ link.href = url;
520
+ link.download = LICENSE_FILE_NAME;
521
+ document.body.appendChild(link);
522
+ link.click();
523
+ document.body.removeChild(link);
524
+ toggleNotification({
525
+ type: "success",
526
+ message: t("license.download.success", "License file downloaded")
527
+ });
528
+ } catch {
529
+ toggleNotification({
530
+ type: "danger",
531
+ message: t("license.download.error", "The license file could not be downloaded.")
532
+ });
533
+ } finally {
534
+ if (url) {
535
+ const objectUrl = url;
536
+ window.setTimeout(() => window.URL.revokeObjectURL(objectUrl), 0);
537
+ }
538
+ }
539
+ };
540
+ const renderBody = () => {
541
+ if (status === "loading") {
542
+ return /* @__PURE__ */ jsx(LoaderArea, { "aria-busy": "true", children: /* @__PURE__ */ jsx(Loader, { children: t("license.loading", "Loading license information...") }) });
543
+ }
544
+ if (status === "error") {
545
+ if (alertDismissed) return null;
546
+ return /* @__PURE__ */ jsx(
547
+ Alert,
548
+ {
549
+ variant: "danger",
550
+ title: t("license.error.title", "License unavailable"),
551
+ closeLabel: t("license.alert.close", "Dismiss message"),
552
+ onClose: () => setAlertDismissed(true),
553
+ action: /* @__PURE__ */ jsx(
554
+ Button,
555
+ {
556
+ variant: "danger-light",
557
+ size: "S",
558
+ startIcon: /* @__PURE__ */ jsx(ArrowClockwise, { "aria-hidden": "true" }),
559
+ onClick: refreshLicenseStatus,
560
+ loading: isRefreshing,
561
+ children: t("license.retry", "Try again")
562
+ }
563
+ ),
564
+ children: licenseErrorMessage(loadError, t)
565
+ }
566
+ );
567
+ }
568
+ return /* @__PURE__ */ jsx(
569
+ LicenseOverview,
570
+ {
571
+ view,
572
+ canUseFullKey,
573
+ revealed,
574
+ alertDismissed,
575
+ onDismissAlert: () => setAlertDismissed(true),
576
+ onToggleReveal: () => setRevealed((current) => !current),
577
+ onCopy: handleCopyLicenseKey,
578
+ onDownload: handleDownloadLicenseKey
579
+ }
580
+ );
581
+ };
582
+ return /* @__PURE__ */ jsxs(Page.Main, { children: [
583
+ pageTitle,
584
+ /* @__PURE__ */ jsxs(Container, { children: [
585
+ /* @__PURE__ */ jsx(StickyHeader, { children: /* @__PURE__ */ jsxs(HeaderRow, { children: [
586
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "flex-start", gap: 1, children: [
587
+ /* @__PURE__ */ jsx(Typography, { tag: "h1", variant: "alpha", textColor: "neutral800", children: t("license.title", "License management") }),
588
+ /* @__PURE__ */ jsx(Typography, { variant: "epsilon", textColor: "neutral600", children: t("license.subtitle", "View the license of your Magic Link plugin") })
589
+ ] }),
590
+ /* @__PURE__ */ jsx(
591
+ GradientButton,
592
+ {
593
+ size: "L",
594
+ startIcon: /* @__PURE__ */ jsx(ArrowClockwise, { "aria-hidden": "true" }),
595
+ onClick: refreshLicenseStatus,
596
+ loading: isRefreshing && status !== "loading",
597
+ disabled: status === "loading",
598
+ children: t("license.refresh", "Refresh status")
599
+ }
600
+ )
601
+ ] }) }),
602
+ /* @__PURE__ */ jsx(Content, { "aria-busy": isRefreshing ? "true" : void 0, children: renderBody() })
603
+ ] })
604
+ ] });
605
+ };
606
+ const LicensePage = () => /* @__PURE__ */ jsx(LicenseGuard, { children: /* @__PURE__ */ jsx(LanguageProvider, { children: /* @__PURE__ */ jsx(LicenseView, {}) }) });
607
+ export {
608
+ LICENSE_FILE_NAME,
609
+ LicenseOverview,
610
+ LicenseView,
611
+ MASK_CHAR,
612
+ buildLicenseFileContent,
613
+ LicensePage as default,
614
+ describeLicense,
615
+ getLicenseView,
616
+ holderName,
617
+ isMaskedKey,
618
+ licenseErrorMessage,
619
+ maskLicenseKey
620
+ };