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
@@ -1,307 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const jsxRuntime = require("react/jsx-runtime");
4
- const react = require("react");
5
- const designSystem = require("@strapi/design-system");
6
- const admin = require("@strapi/strapi/admin");
7
- const icons = require("@strapi/icons");
8
- const styled = require("styled-components");
9
- const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
10
- const styled__default = /* @__PURE__ */ _interopDefault(styled);
11
- const theme = {
12
- colors: {
13
- neutral: { 200: "#E5E7EB" }
14
- },
15
- shadows: { sm: "0 1px 3px rgba(0,0,0,0.1)" },
16
- borderRadius: { lg: "12px" }
17
- };
18
- const fadeIn = styled.keyframes`
19
- from { opacity: 0; transform: translateY(10px); }
20
- to { opacity: 1; transform: translateY(0); }
21
- `;
22
- const shimmer = styled.keyframes`
23
- 0% { background-position: -200% 0; }
24
- 100% { background-position: 200% 0; }
25
- `;
26
- const Container = styled__default.default(designSystem.Box)`
27
- animation: ${fadeIn} 0.5s;
28
- max-width: 1400px;
29
- margin: 0 auto;
30
- `;
31
- const StickySaveBar = styled__default.default(designSystem.Box)`
32
- position: sticky;
33
- top: 0;
34
- z-index: 10;
35
- background: white;
36
- border-bottom: 1px solid ${theme.colors.neutral[200]};
37
- box-shadow: ${theme.shadows.sm};
38
- `;
39
- const LicenseKeyBanner = styled__default.default(designSystem.Box)`
40
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
41
- border-radius: ${theme.borderRadius.lg};
42
- padding: 28px 32px;
43
- color: white;
44
- position: relative;
45
- overflow: hidden;
46
- box-shadow: 0 4px 20px rgba(102, 126, 234, 0.25);
47
- margin-bottom: 24px;
48
-
49
- &::after {
50
- content: '';
51
- position: absolute;
52
- top: -50%;
53
- right: -50%;
54
- width: 200%;
55
- height: 200%;
56
- background: linear-gradient(
57
- 45deg,
58
- transparent,
59
- rgba(255, 255, 255, 0.08),
60
- transparent
61
- );
62
- animation: ${shimmer} 3s infinite;
63
- pointer-events: none;
64
- z-index: 0;
65
- }
66
-
67
- & > * {
68
- position: relative;
69
- z-index: 1;
70
- }
71
- `;
72
- styled__default.default(designSystem.Flex)`
73
- padding: 16px 0;
74
- border-bottom: 1px solid ${theme.colors.neutral[200]};
75
-
76
- &:last-child {
77
- border-bottom: none;
78
- }
79
- `;
80
- const LoaderContainer = styled__default.default(designSystem.Flex)`
81
- min-height: 400px;
82
- align-items: center;
83
- justify-content: center;
84
- flex-direction: column;
85
- gap: 16px;
86
- `;
87
- const LicensePage = () => {
88
- const { get } = admin.useFetchClient();
89
- const { toggleNotification } = admin.useNotification();
90
- const [loading, setLoading] = react.useState(true);
91
- const [licenseData, setLicenseData] = react.useState(null);
92
- const [error, setError] = react.useState(null);
93
- const fetchLicenseStatus = async () => {
94
- setLoading(true);
95
- setError(null);
96
- try {
97
- const response = await get("/magic-link/license/status");
98
- setLicenseData(response.data);
99
- } catch (err) {
100
- console.error("Error fetching license:", err);
101
- setError("Failed to load license information");
102
- } finally {
103
- setLoading(false);
104
- }
105
- };
106
- const handleCopyLicenseKey = async () => {
107
- try {
108
- await navigator.clipboard.writeText(licenseData?.data?.licenseKey || "");
109
- toggleNotification({
110
- type: "success",
111
- message: "License key copied to clipboard!",
112
- title: "Copied"
113
- });
114
- } catch (err) {
115
- toggleNotification({
116
- type: "danger",
117
- message: "Failed to copy license key",
118
- title: "Error"
119
- });
120
- }
121
- };
122
- const handleDownloadLicenseKey = () => {
123
- try {
124
- const data2 = licenseData?.data || {};
125
- const licenseKey = data2.licenseKey || "";
126
- const email = data2.email || "N/A";
127
- const firstName = data2.firstName || "";
128
- const lastName = data2.lastName || "";
129
- const fullName = `${firstName} ${lastName}`.trim() || "N/A";
130
- const content = `Magic Link Plugin License Key
131
- ═══════════════════════════════════════
132
-
133
- License Key: ${licenseKey}
134
-
135
- License Holder Information:
136
- ──────────────────────────────────────
137
- Name: ${fullName}
138
- Email: ${email}
139
-
140
- License Status:
141
- ──────────────────────────────────────
142
- Status: ${data2.isActive ? "ACTIVE" : "INACTIVE"}
143
- Expires: ${data2.expiresAt ? new Date(data2.expiresAt).toLocaleDateString() : "Never"}
144
-
145
- ═══════════════════════════════════════
146
- Generated: ${(/* @__PURE__ */ new Date()).toLocaleString()}
147
- `;
148
- const blob = new Blob([content], { type: "text/plain" });
149
- const url = window.URL.createObjectURL(blob);
150
- const link = document.createElement("a");
151
- link.href = url;
152
- link.download = `magic-link-license-${licenseKey.substring(0, 8)}.txt`;
153
- document.body.appendChild(link);
154
- link.click();
155
- document.body.removeChild(link);
156
- window.URL.revokeObjectURL(url);
157
- toggleNotification({
158
- type: "success",
159
- message: "License key downloaded successfully!",
160
- title: "Downloaded"
161
- });
162
- } catch (err) {
163
- toggleNotification({
164
- type: "danger",
165
- message: "Failed to download license key",
166
- title: "Error"
167
- });
168
- }
169
- };
170
- react.useEffect(() => {
171
- fetchLicenseStatus();
172
- }, []);
173
- if (loading) {
174
- return /* @__PURE__ */ jsxRuntime.jsx(Container, { children: /* @__PURE__ */ jsxRuntime.jsx(LoaderContainer, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Loader, { children: "Loading license information..." }) }) });
175
- }
176
- if (error) {
177
- return /* @__PURE__ */ jsxRuntime.jsx(Container, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 8, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Alert, { variant: "danger", title: "Error", children: error }) }) });
178
- }
179
- const isValid = licenseData?.valid !== false;
180
- const hasKey = !!licenseData?.hasKey;
181
- const data = licenseData?.data || {};
182
- return /* @__PURE__ */ jsxRuntime.jsxs(Container, { children: [
183
- /* @__PURE__ */ jsxRuntime.jsx(StickySaveBar, { paddingTop: 5, paddingBottom: 5, paddingLeft: 6, paddingRight: 6, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", children: [
184
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 1, children: [
185
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "alpha", fontWeight: "bold", children: "License Management" }),
186
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "epsilon", textColor: "neutral600", children: "View your Magic Link plugin license" })
187
- ] }),
188
- /* @__PURE__ */ jsxRuntime.jsx(
189
- designSystem.Button,
190
- {
191
- startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.ArrowClockwise, {}),
192
- onClick: fetchLicenseStatus,
193
- size: "L",
194
- style: {
195
- background: "linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
196
- color: "white",
197
- fontWeight: "600",
198
- border: "none"
199
- },
200
- children: "Refresh Status"
201
- }
202
- )
203
- ] }) }),
204
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { paddingTop: 6, paddingLeft: 6, paddingRight: 6, paddingBottom: 10, children: [
205
- hasKey && isValid ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Alert, { variant: "success", title: "License Active", children: "License key registered. Thanks for supporting the plugin!" }) : hasKey && !isValid ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Alert, { variant: "warning", title: "License Verification", children: "We couldn't reach the license server right now. The plugin keeps working — try Refresh Status later." }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Alert, { variant: "default", title: "No license key registered", children: 'All Magic Link features are available without activation. If you want to register a license key, use the form below or run "Auto-Activate" to generate one for your admin email.' }),
206
- data.licenseKey && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { marginTop: 6, children: /* @__PURE__ */ jsxRuntime.jsx(LicenseKeyBanner, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "flex-start", children: [
207
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { style: { flex: 1 }, children: [
208
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", style: { color: "rgba(255,255,255,0.8)", marginBottom: "12px", textTransform: "uppercase", fontSize: "11px", letterSpacing: "0.5px", display: "block" }, children: "License Key" }),
209
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { style: { color: "white", fontFamily: "monospace", fontSize: "28px", fontWeight: "bold", wordBreak: "break-all", marginBottom: "16px" }, children: data.licenseKey }),
210
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
211
- /* @__PURE__ */ jsxRuntime.jsx(
212
- designSystem.Button,
213
- {
214
- onClick: handleCopyLicenseKey,
215
- startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Duplicate, {}),
216
- size: "S",
217
- variant: "secondary",
218
- style: {
219
- backgroundColor: "rgba(255,255,255,0.2)",
220
- color: "white",
221
- border: "1px solid rgba(255,255,255,0.3)",
222
- fontWeight: "600"
223
- },
224
- children: "Copy Key"
225
- }
226
- ),
227
- /* @__PURE__ */ jsxRuntime.jsx(
228
- designSystem.Button,
229
- {
230
- onClick: handleDownloadLicenseKey,
231
- startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Download, {}),
232
- size: "S",
233
- variant: "secondary",
234
- style: {
235
- backgroundColor: "rgba(255,255,255,0.2)",
236
- color: "white",
237
- border: "1px solid rgba(255,255,255,0.3)",
238
- fontWeight: "600"
239
- },
240
- children: "Download as TXT"
241
- }
242
- )
243
- ] })
244
- ] }),
245
- /* @__PURE__ */ jsxRuntime.jsx(
246
- designSystem.Badge,
247
- {
248
- backgroundColor: data.isActive ? "success100" : "danger100",
249
- textColor: data.isActive ? "success700" : "danger700",
250
- style: { fontSize: "11px", fontWeight: "700", padding: "6px 12px", marginLeft: "16px", flexShrink: 0 },
251
- children: data.isActive ? "ACTIVE" : "INACTIVE"
252
- }
253
- )
254
- ] }) }) }),
255
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { marginTop: 6, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Accordion.Root, { defaultValue: ["account", "details", "features", "status"], children: [
256
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Accordion.Item, { value: "account", children: [
257
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Accordion.Header, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Accordion.Trigger, { icon: icons.User, children: "Account Information" }) }),
258
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Accordion.Content, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 6, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 8, wrap: "wrap", children: [
259
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { style: { flex: "1", minWidth: "200px" }, children: [
260
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", textColor: "neutral600", textTransform: "uppercase", style: { marginBottom: "8px", display: "block" }, children: "Email Address" }),
261
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", fontWeight: "semiBold", children: data.email || "Not provided" })
262
- ] }),
263
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { style: { flex: "1", minWidth: "200px" }, children: [
264
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", textColor: "neutral600", textTransform: "uppercase", style: { marginBottom: "8px", display: "block" }, children: "License Holder" }),
265
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", fontWeight: "semiBold", children: data.firstName && data.lastName ? `${data.firstName} ${data.lastName}` : "Not specified" })
266
- ] })
267
- ] }) }) })
268
- ] }),
269
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Accordion.Item, { value: "details", children: [
270
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Accordion.Header, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Accordion.Trigger, { icon: icons.Shield, children: "License Details" }) }),
271
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Accordion.Content, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 6, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 8, wrap: "wrap", children: [
272
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { style: { flex: "1", minWidth: "180px" }, children: [
273
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", textColor: "neutral600", textTransform: "uppercase", style: { marginBottom: "8px", display: "block" }, children: data.isExpired ? "Expired On" : "Expires On" }),
274
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", fontWeight: "semiBold", children: data.expiresAt ? new Date(data.expiresAt).toLocaleDateString("en-US", {
275
- year: "numeric",
276
- month: "long",
277
- day: "numeric"
278
- }) : "Never" })
279
- ] }),
280
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { style: { flex: "1", minWidth: "180px" }, children: [
281
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", textColor: "neutral600", textTransform: "uppercase", style: { marginBottom: "8px", display: "block" }, children: "Device Name" }),
282
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", fontWeight: "semiBold", children: data.deviceName || "Unknown" })
283
- ] }),
284
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { style: { flex: "1", minWidth: "180px" }, children: [
285
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", textColor: "neutral600", textTransform: "uppercase", style: { marginBottom: "8px", display: "block" }, children: "IP Address" }),
286
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", fontWeight: "semiBold", children: data.ipAddress || "Not detected" })
287
- ] })
288
- ] }) }) })
289
- ] }),
290
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Accordion.Item, { value: "status", children: [
291
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Accordion.Header, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Accordion.Trigger, { icon: icons.ChartBubble, children: "System Status" }) }),
292
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Accordion.Content, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 6, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 8, wrap: "wrap", children: [
293
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { style: { flex: "1", minWidth: "150px" }, children: [
294
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", textColor: "neutral600", textTransform: "uppercase", style: { marginBottom: "8px", display: "block" }, children: "License Status" }),
295
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", fontWeight: "semiBold", children: hasKey ? data.isActive ? "Active" : "Inactive" : "No key registered" })
296
- ] }),
297
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { style: { flex: "1", minWidth: "150px" }, children: [
298
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", textColor: "neutral600", textTransform: "uppercase", style: { marginBottom: "8px", display: "block" }, children: "Plugin Mode" }),
299
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", fontWeight: "semiBold", children: "Fully unlocked" })
300
- ] })
301
- ] }) }) })
302
- ] })
303
- ] }) })
304
- ] })
305
- ] });
306
- };
307
- exports.default = LicensePage;
@@ -1,330 +0,0 @@
1
- import { jsx, jsxs } from "react/jsx-runtime";
2
- import { useState, useCallback, useEffect } from "react";
3
- import styled from "styled-components";
4
- import { useIntl } from "react-intl";
5
- import { Flex, Loader, Box, Typography, Button, TextInput, Badge } from "@strapi/design-system";
6
- import { Phone, Check, Message, ArrowClockwise } from "@strapi/icons";
7
- import { useNotification, useFetchClient } from "@strapi/strapi/admin";
8
- import { g as getTrad } from "./index-DQUYFsLT.mjs";
9
- const theme = {
10
- colors: {
11
- primary: { 700: "#075985", 100: "#E0F2FE", 50: "#F0F9FF" },
12
- success: { 600: "#16A34A", 700: "#15803D", 100: "#DCFCE7" },
13
- danger: { 700: "#B91C1C", 100: "#FEE2E2" },
14
- warning: { 700: "#A16207", 100: "#FEF3C7", 50: "#FFFBEB" },
15
- whatsapp: { 600: "#25D366" },
16
- neutral: { 50: "#F9FAFB", 100: "#F3F4F6", 200: "#E5E7EB", 800: "#1F2937" }
17
- },
18
- shadows: { md: "0 4px 6px rgba(0,0,0,0.1)" },
19
- borderRadius: { md: "8px", lg: "12px" }
20
- };
21
- const Container = styled(Box)`
22
- max-width: 900px;
23
- margin: 0 auto;
24
- padding: 32px;
25
- `;
26
- const Card = styled(Box)`
27
- background: white;
28
- border-radius: ${theme.borderRadius.lg};
29
- box-shadow: ${theme.shadows.md};
30
- padding: 24px;
31
- margin-bottom: 24px;
32
- `;
33
- const QRCodeContainer = styled(Box)`
34
- display: flex;
35
- justify-content: center;
36
- align-items: center;
37
- padding: 24px;
38
- background: ${theme.colors.neutral[50]};
39
- border-radius: ${theme.borderRadius.lg};
40
- border: 2px dashed ${theme.colors.neutral[200]};
41
- min-height: 280px;
42
-
43
- img {
44
- max-width: 256px;
45
- border-radius: ${theme.borderRadius.md};
46
- }
47
- `;
48
- const StatusBadge = styled(Badge)`
49
- background: ${(props) => {
50
- switch (props.$status) {
51
- case "connected":
52
- return theme.colors.success[100];
53
- case "connecting":
54
- return theme.colors.warning[100];
55
- case "qr_pending":
56
- return theme.colors.primary[100];
57
- default:
58
- return theme.colors.danger[100];
59
- }
60
- }};
61
- color: ${(props) => {
62
- switch (props.$status) {
63
- case "connected":
64
- return theme.colors.success[700];
65
- case "connecting":
66
- return theme.colors.warning[700];
67
- case "qr_pending":
68
- return theme.colors.primary[700];
69
- default:
70
- return theme.colors.danger[700];
71
- }
72
- }};
73
- font-weight: 600;
74
- padding: 8px 16px;
75
- font-size: 14px;
76
- `;
77
- const WhatsAppIcon = styled.div`
78
- width: 48px;
79
- height: 48px;
80
- border-radius: 50%;
81
- background: ${theme.colors.whatsapp[600]};
82
- display: flex;
83
- align-items: center;
84
- justify-content: center;
85
- color: white;
86
- font-size: 24px;
87
- `;
88
- const WhatsAppPage = () => {
89
- const { formatMessage } = useIntl();
90
- const { toggleNotification } = useNotification();
91
- const { get, post } = useFetchClient();
92
- const [loading, setLoading] = useState(true);
93
- const [status, setStatus] = useState({
94
- available: false,
95
- status: "disconnected",
96
- qrCode: null,
97
- session: null,
98
- delegatedTo: null
99
- });
100
- const [settings, setSettings] = useState({
101
- whatsapp_enabled: false,
102
- whatsapp_app_name: "Magic Link"
103
- });
104
- const [testPhone, setTestPhone] = useState("");
105
- const [testMessage, setTestMessage] = useState("");
106
- const [sending, setSending] = useState(false);
107
- const [connecting, setConnecting] = useState(false);
108
- const fetchStatus = useCallback(async () => {
109
- try {
110
- const response = await get("/magic-link/whatsapp/status");
111
- setStatus(response.data);
112
- } catch (error) {
113
- console.error("Error fetching WhatsApp status:", error);
114
- }
115
- }, [get]);
116
- const fetchSettings = useCallback(async () => {
117
- try {
118
- const response = await get("/magic-link/settings");
119
- setSettings(response.data || {});
120
- } catch (error) {
121
- console.error("Error fetching settings:", error);
122
- }
123
- }, [get]);
124
- useEffect(() => {
125
- const init = async () => {
126
- setLoading(true);
127
- await Promise.all([fetchStatus(), fetchSettings()]);
128
- setLoading(false);
129
- };
130
- init();
131
- }, [fetchStatus, fetchSettings]);
132
- useEffect(() => {
133
- if (status.status === "connecting" || status.status === "qr_pending") {
134
- const interval = setInterval(fetchStatus, 3e3);
135
- return () => clearInterval(interval);
136
- }
137
- }, [status.status, fetchStatus]);
138
- const handleConnect = async () => {
139
- setConnecting(true);
140
- try {
141
- await post("/magic-link/whatsapp/connect");
142
- await fetchStatus();
143
- toggleNotification({
144
- type: "success",
145
- message: formatMessage({ id: getTrad("whatsapp.notifications.connecting") })
146
- });
147
- } catch (error) {
148
- toggleNotification({
149
- type: "warning",
150
- message: error.response?.data?.error?.message || "Connection failed"
151
- });
152
- }
153
- setConnecting(false);
154
- };
155
- const handleDisconnect = async () => {
156
- try {
157
- await post("/magic-link/whatsapp/disconnect");
158
- await fetchStatus();
159
- toggleNotification({
160
- type: "success",
161
- message: formatMessage({ id: getTrad("whatsapp.notifications.disconnected") })
162
- });
163
- } catch (error) {
164
- toggleNotification({
165
- type: "warning",
166
- message: "Disconnect failed"
167
- });
168
- }
169
- };
170
- const handleTestMessage = async () => {
171
- if (!testPhone) {
172
- toggleNotification({
173
- type: "warning",
174
- message: formatMessage({ id: getTrad("whatsapp.notifications.phoneRequired") })
175
- });
176
- return;
177
- }
178
- setSending(true);
179
- try {
180
- const response = await post("/magic-link/whatsapp/test-message", {
181
- phoneNumber: testPhone,
182
- message: testMessage || void 0
183
- });
184
- if (response.data.success) {
185
- toggleNotification({
186
- type: "success",
187
- message: formatMessage({ id: getTrad("whatsapp.notifications.messageSent") })
188
- });
189
- setTestMessage("");
190
- } else {
191
- toggleNotification({
192
- type: "warning",
193
- message: response.data.error || "Failed to send"
194
- });
195
- }
196
- } catch (error) {
197
- toggleNotification({
198
- type: "warning",
199
- message: error.response?.data?.error?.message || "Failed to send message"
200
- });
201
- }
202
- setSending(false);
203
- };
204
- const getStatusLabel = () => {
205
- switch (status.status) {
206
- case "connected":
207
- return "Connected";
208
- case "connecting":
209
- return "Connecting...";
210
- case "qr_pending":
211
- return "Scan QR Code";
212
- case "not_installed":
213
- return "Baileys not installed";
214
- default:
215
- return "Disconnected";
216
- }
217
- };
218
- if (loading) {
219
- return /* @__PURE__ */ jsx(Container, { children: /* @__PURE__ */ jsx(Flex, { justifyContent: "center", padding: 8, children: /* @__PURE__ */ jsx(Loader, {}) }) });
220
- }
221
- return /* @__PURE__ */ jsxs(Container, { children: [
222
- /* @__PURE__ */ jsxs(Box, { marginBottom: 6, children: [
223
- /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "flex-start", marginBottom: 2, children: [
224
- /* @__PURE__ */ jsxs(Flex, { alignItems: "center", gap: 4, children: [
225
- /* @__PURE__ */ jsx(WhatsAppIcon, { children: /* @__PURE__ */ jsx(Phone, {}) }),
226
- /* @__PURE__ */ jsx(Typography, { variant: "alpha", fontWeight: "bold", children: formatMessage({ id: getTrad("whatsapp.title") }) })
227
- ] }),
228
- /* @__PURE__ */ jsx(StatusBadge, { $status: status.status, children: getStatusLabel() })
229
- ] }),
230
- /* @__PURE__ */ jsx(Typography, { variant: "epsilon", textColor: "neutral600", style: { marginLeft: "64px" }, children: formatMessage({ id: getTrad("whatsapp.subtitle") }) })
231
- ] }),
232
- status.delegatedTo === "magic-mail" && /* @__PURE__ */ jsxs(Card, { style: { background: theme.colors.primary[50], border: `2px solid ${theme.colors.primary[100]}` }, children: [
233
- /* @__PURE__ */ jsx(Typography, { variant: "delta", fontWeight: "bold", style: { display: "block", marginBottom: "12px" }, children: "WhatsApp managed by Magic-Mail" }),
234
- /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", style: { display: "block", lineHeight: "1.6", marginBottom: "16px" }, children: "Magic-Mail plugin is installed and handles the WhatsApp connection. You only need to scan the QR code once in Magic-Mail, and both plugins will share the same WhatsApp session." }),
235
- /* @__PURE__ */ jsxs(Box, { style: { padding: "12px", background: theme.colors.neutral[100], borderRadius: theme.borderRadius.md }, children: [
236
- /* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: "bold", style: { display: "block", marginBottom: "8px" }, children: "Go to: Plugins → Magic-Mail → WhatsApp" }),
237
- /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral500", children: "All WhatsApp settings and connection management is done there." })
238
- ] })
239
- ] }),
240
- !status.available && !status.delegatedTo && /* @__PURE__ */ jsxs(Card, { style: { background: theme.colors.warning[50], border: `2px solid ${theme.colors.warning[100]}` }, children: [
241
- /* @__PURE__ */ jsx(Typography, { variant: "delta", fontWeight: "bold", style: { display: "block", marginBottom: "12px" }, children: formatMessage({ id: getTrad("whatsapp.notInstalled.title") }) }),
242
- /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", style: { display: "block", lineHeight: "1.6", marginBottom: "16px" }, children: formatMessage({ id: getTrad("whatsapp.notInstalled.description") }) }),
243
- /* @__PURE__ */ jsx(Box, { style: { padding: "12px", background: theme.colors.neutral[100], borderRadius: theme.borderRadius.md }, children: /* @__PURE__ */ jsx("code", { style: { fontSize: "14px" }, children: "npm install @whiskeysockets/baileys pino" }) })
244
- ] }),
245
- status.available && !status.delegatedTo && /* @__PURE__ */ jsxs(Card, { children: [
246
- /* @__PURE__ */ jsx(Typography, { variant: "delta", fontWeight: "bold", marginBottom: 4, children: formatMessage({ id: getTrad("whatsapp.connection.title") }) }),
247
- (status.status === "qr_pending" || status.status === "disconnected" || status.status === "connecting") && /* @__PURE__ */ jsx(QRCodeContainer, { children: status.qrCode ? /* @__PURE__ */ jsxs(Box, { textAlign: "center", children: [
248
- /* @__PURE__ */ jsx("img", { src: status.qrCode, alt: "WhatsApp QR Code" }),
249
- /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", style: { marginTop: "16px", display: "block" }, children: formatMessage({ id: getTrad("whatsapp.qr.instruction") }) })
250
- ] }) : /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "center", gap: 4, children: [
251
- /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: status.status === "connecting" ? formatMessage({ id: getTrad("whatsapp.connection.generating") }) : formatMessage({ id: getTrad("whatsapp.connection.clickToConnect") }) }),
252
- status.status !== "connecting" && /* @__PURE__ */ jsx(
253
- Button,
254
- {
255
- onClick: handleConnect,
256
- loading: connecting,
257
- startIcon: /* @__PURE__ */ jsx(Phone, {}),
258
- style: { background: theme.colors.whatsapp[600] },
259
- children: formatMessage({ id: getTrad("whatsapp.actions.connect") })
260
- }
261
- )
262
- ] }) }),
263
- status.status === "connected" && /* @__PURE__ */ jsxs(Box, { children: [
264
- /* @__PURE__ */ jsxs(
265
- Flex,
266
- {
267
- alignItems: "flex-start",
268
- justifyContent: "space-between",
269
- padding: 4,
270
- background: "success100",
271
- style: { borderRadius: theme.borderRadius.md, marginBottom: "16px" },
272
- children: [
273
- /* @__PURE__ */ jsxs(Flex, { alignItems: "flex-start", gap: 3, children: [
274
- /* @__PURE__ */ jsx(Check, { style: { color: theme.colors.success[600], marginTop: "4px" } }),
275
- /* @__PURE__ */ jsxs(Box, { children: [
276
- /* @__PURE__ */ jsx(Typography, { variant: "delta", fontWeight: "bold", style: { display: "block", marginBottom: "8px" }, children: formatMessage({ id: getTrad("whatsapp.connected.title") }) }),
277
- status.session && /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral600", style: { display: "block" }, children: [
278
- status.session.phoneNumber,
279
- " • ",
280
- status.session.name || "WhatsApp User"
281
- ] })
282
- ] })
283
- ] }),
284
- /* @__PURE__ */ jsx(Button, { variant: "danger", onClick: handleDisconnect, size: "S", children: formatMessage({ id: getTrad("whatsapp.actions.disconnect") }) })
285
- ]
286
- }
287
- ),
288
- /* @__PURE__ */ jsxs(Box, { style: { marginTop: "24px" }, children: [
289
- /* @__PURE__ */ jsx(Typography, { variant: "delta", fontWeight: "bold", style: { display: "block", marginBottom: "16px" }, children: formatMessage({ id: getTrad("whatsapp.test.title") }) }),
290
- /* @__PURE__ */ jsxs(Flex, { gap: 3, alignItems: "flex-start", children: [
291
- /* @__PURE__ */ jsx(Box, { style: { flex: 1 }, children: /* @__PURE__ */ jsx(
292
- TextInput,
293
- {
294
- placeholder: "491234567890",
295
- value: testPhone,
296
- onChange: (e) => setTestPhone(e.target.value)
297
- }
298
- ) }),
299
- /* @__PURE__ */ jsx(
300
- Button,
301
- {
302
- onClick: handleTestMessage,
303
- loading: sending,
304
- startIcon: /* @__PURE__ */ jsx(Message, {}),
305
- children: formatMessage({ id: getTrad("whatsapp.actions.sendTest") })
306
- }
307
- )
308
- ] })
309
- ] })
310
- ] }),
311
- /* @__PURE__ */ jsx(Flex, { justifyContent: "center", marginTop: 4, children: /* @__PURE__ */ jsx(Button, { variant: "tertiary", onClick: fetchStatus, startIcon: /* @__PURE__ */ jsx(ArrowClockwise, {}), children: formatMessage({ id: getTrad("whatsapp.actions.refresh") }) }) })
312
- ] }),
313
- !status.delegatedTo && /* @__PURE__ */ jsxs(Card, { style: { background: theme.colors.primary[50], border: `2px solid ${theme.colors.primary[100]}` }, children: [
314
- /* @__PURE__ */ jsx(Typography, { variant: "delta", fontWeight: "bold", style: { display: "block", marginBottom: "12px" }, children: formatMessage({ id: getTrad("whatsapp.info.title") }) }),
315
- /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral700", style: { display: "block", lineHeight: "1.6", marginBottom: "16px" }, children: formatMessage({ id: getTrad("whatsapp.info.description") }) }),
316
- /* @__PURE__ */ jsxs(Box, { style: { marginTop: "16px" }, children: [
317
- /* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: "bold", style: { display: "block", marginBottom: "8px" }, children: formatMessage({ id: getTrad("whatsapp.info.apiExample") }) }),
318
- /* @__PURE__ */ jsx(Box, { style: { padding: "12px", background: theme.colors.neutral[800], borderRadius: theme.borderRadius.md, color: "#fff", fontFamily: "monospace", fontSize: "13px" }, children: /* @__PURE__ */ jsx("pre", { style: { margin: 0, whiteSpace: "pre-wrap" }, children: `POST /api/magic-link/send-link
319
- {
320
- "email": "user@example.com",
321
- "phoneNumber": "491234567890",
322
- "delivery": "whatsapp"
323
- }` }) })
324
- ] })
325
- ] })
326
- ] });
327
- };
328
- export {
329
- WhatsAppPage as default
330
- };