strapi-plugin-magic-mail 3.0.4 → 4.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 +192 -0
- package/README.md +625 -1336
- package/dist/admin/App-Bi6mzEff.mjs +10973 -0
- package/dist/admin/App-aaEU9cpu.js +10976 -0
- package/dist/admin/PluginSettings-BR2-PVHx.mjs +705 -0
- package/dist/admin/PluginSettings-DbTkOzCh.js +707 -0
- package/dist/admin/analyticsLogs-BCY0Zv3Y.mjs +685 -0
- package/dist/admin/analyticsLogs-DNU09hoB.js +686 -0
- package/dist/admin/de-B5bPRK6l.js +717 -0
- package/dist/admin/de-BqYqMhOn.mjs +717 -0
- package/dist/admin/en-8xANOQdO.mjs +717 -0
- package/dist/admin/en-Bk5eVrtc.js +717 -0
- package/dist/admin/es-BfPsCsam.js +717 -0
- package/dist/admin/es-DgpTufJ_.mjs +717 -0
- package/dist/admin/fr-BuotP0Gc.mjs +717 -0
- package/dist/admin/fr-IQ3KPUoS.js +717 -0
- package/dist/admin/index-QGqbGw6k.js +102 -0
- package/dist/admin/index-qvFJ-a0Q.mjs +103 -0
- package/dist/admin/index.js +2 -99
- package/dist/admin/index.mjs +2 -99
- package/dist/admin/pt-BI63Lncs.js +717 -0
- package/dist/admin/pt-DBZu2cDY.mjs +717 -0
- package/dist/server/index.js +4655 -3374
- package/dist/server/index.mjs +4655 -3374
- package/package.json +54 -48
- package/strapi-admin.js +3 -2
- package/strapi-server.js +3 -2
- package/dist/admin/App-D3TchJYB.js +0 -8028
- package/dist/admin/App-DkyWmEgv.mjs +0 -8007
- package/dist/admin/PluginSettings-B6TdA1qd.js +0 -388
- package/dist/admin/PluginSettings-DTqdZ-nW.mjs +0 -386
- package/dist/admin/StyledButtons-aq1EWrQ-.mjs +0 -433
- package/dist/admin/StyledButtons-jHF7TXal.js +0 -434
- package/dist/admin/de-DKEFnBGC.js +0 -63
- package/dist/admin/de-SKsLY2de.mjs +0 -63
- package/dist/admin/en-CvtZE1HH.js +0 -63
- package/dist/admin/en-Ds2Q7fue.mjs +0 -63
- package/dist/admin/es-BEvzLtTg.mjs +0 -63
- package/dist/admin/es-BbLLes3D.js +0 -63
- package/dist/admin/fr-CRpwImX6.mjs +0 -63
- package/dist/admin/fr-nH9uN6s9.js +0 -63
- package/dist/admin/pt-CjOe2N_n.mjs +0 -63
- package/dist/admin/pt-qt_48NxL.js +0 -63
- package/pics/EmailAccountDashboard.png +0 -0
- package/pics/Emailaddccount1.png +0 -0
- package/pics/RoutingEmailTemplates.png +0 -0
- package/pics/Templates.png +0 -0
- package/pics/codesnippet.png +0 -0
- package/pics/emailAnalytics.png +0 -0
- package/pics/emailDesigner.png +0 -0
- package/pics/emailTemplateSendTest.png +0 -0
- package/pics/emailVersionHistory.png +0 -0
- package/pics/emailaddaccount2.png +0 -0
- package/pics/emailaddaccount3.png +0 -0
|
@@ -0,0 +1,685 @@
|
|
|
1
|
+
import { useRBAC, Page } from "@strapi/strapi/admin";
|
|
2
|
+
import { p as pluginPermissions, g as getTranslation } from "./index-qvFJ-a0Q.mjs";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
import { useCallback, forwardRef, useRef, useLayoutEffect, useState, useEffect } from "react";
|
|
5
|
+
import { Button, Tooltip, Dialog, Flex, Typography } from "@strapi/design-system";
|
|
6
|
+
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
|
|
7
|
+
import styled, { css } from "styled-components";
|
|
8
|
+
import { useBlocker } from "react-router-dom";
|
|
9
|
+
import { useIntl } from "react-intl";
|
|
10
|
+
const ROUTE_CAPABILITIES = [
|
|
11
|
+
...pluginPermissions.read,
|
|
12
|
+
...pluginPermissions.manage,
|
|
13
|
+
...pluginPermissions.testSend
|
|
14
|
+
];
|
|
15
|
+
const useMagicMailRBAC = () => {
|
|
16
|
+
const { allowedActions = {}, isLoading = false, error } = useRBAC(ROUTE_CAPABILITIES) ?? {};
|
|
17
|
+
const settled = !isLoading && !error;
|
|
18
|
+
const allowed = (key) => settled && allowedActions?.[key] === true;
|
|
19
|
+
const canRead = allowed("canRead");
|
|
20
|
+
const canManage = allowed("canManage");
|
|
21
|
+
return {
|
|
22
|
+
canRead,
|
|
23
|
+
canManage,
|
|
24
|
+
canTestSend: allowed("canTestSend"),
|
|
25
|
+
isReadOnly: canRead && !canManage,
|
|
26
|
+
isLoading: Boolean(isLoading),
|
|
27
|
+
error,
|
|
28
|
+
permissions: pluginPermissions
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
const useMagicMailIntl = () => {
|
|
32
|
+
const intl = useIntl();
|
|
33
|
+
const t = useCallback(
|
|
34
|
+
(key, defaultMessage, values) => intl.formatMessage({ id: getTranslation(key), defaultMessage }, values),
|
|
35
|
+
[intl]
|
|
36
|
+
);
|
|
37
|
+
return { t, formatMessage: intl.formatMessage, locale: intl.locale };
|
|
38
|
+
};
|
|
39
|
+
const LIGHT_FALLBACK_COLORS = Object.freeze({
|
|
40
|
+
alternative100: "#f6ecfc",
|
|
41
|
+
alternative200: "#e0c1f4",
|
|
42
|
+
alternative500: "#ac73e6",
|
|
43
|
+
alternative600: "#9736e8",
|
|
44
|
+
alternative700: "#8312d1",
|
|
45
|
+
neutral0: "#ffffff",
|
|
46
|
+
neutral100: "#f6f6f9",
|
|
47
|
+
neutral150: "#eaeaef",
|
|
48
|
+
neutral200: "#dcdce4",
|
|
49
|
+
neutral300: "#c0c0cf",
|
|
50
|
+
neutral400: "#a5a5ba",
|
|
51
|
+
neutral500: "#8e8ea9",
|
|
52
|
+
neutral600: "#666687",
|
|
53
|
+
neutral700: "#4a4a6a",
|
|
54
|
+
neutral800: "#32324d",
|
|
55
|
+
neutral900: "#212134",
|
|
56
|
+
neutral1000: "#181826",
|
|
57
|
+
primary100: "#f0f0ff",
|
|
58
|
+
primary200: "#d9d8ff",
|
|
59
|
+
primary500: "#7b79ff",
|
|
60
|
+
primary600: "#4945ff",
|
|
61
|
+
primary700: "#271fe0",
|
|
62
|
+
secondary100: "#eaf5ff",
|
|
63
|
+
secondary200: "#b8e1ff",
|
|
64
|
+
secondary500: "#66b7f1",
|
|
65
|
+
secondary600: "#0c75af",
|
|
66
|
+
secondary700: "#006096",
|
|
67
|
+
success100: "#eafbe7",
|
|
68
|
+
success200: "#c6f0c2",
|
|
69
|
+
success500: "#5cb176",
|
|
70
|
+
success600: "#328048",
|
|
71
|
+
success700: "#2f6846",
|
|
72
|
+
warning100: "#fdf4dc",
|
|
73
|
+
warning200: "#fae7b9",
|
|
74
|
+
warning500: "#f29d41",
|
|
75
|
+
warning600: "#d9822f",
|
|
76
|
+
warning700: "#be5d01",
|
|
77
|
+
danger100: "#fcecea",
|
|
78
|
+
danger200: "#f5c0b8",
|
|
79
|
+
danger500: "#ee5e52",
|
|
80
|
+
danger600: "#d02b20",
|
|
81
|
+
danger700: "#b72b1a"
|
|
82
|
+
});
|
|
83
|
+
const FONT_SIZE_FALLBACK = Object.freeze([
|
|
84
|
+
"1.1rem",
|
|
85
|
+
// 0 -> 11px
|
|
86
|
+
"1.2rem",
|
|
87
|
+
// 1 -> 12px
|
|
88
|
+
"1.4rem",
|
|
89
|
+
// 2 -> 14px
|
|
90
|
+
"1.6rem",
|
|
91
|
+
// 3 -> 16px
|
|
92
|
+
"1.8rem",
|
|
93
|
+
// 4 -> 18px
|
|
94
|
+
"2rem",
|
|
95
|
+
// 5 -> 20px
|
|
96
|
+
"2.8rem",
|
|
97
|
+
// 6 -> 28px
|
|
98
|
+
"3.2rem"
|
|
99
|
+
// 7 -> 32px
|
|
100
|
+
]);
|
|
101
|
+
const color = (token) => (props) => props?.theme?.colors?.[token] ?? LIGHT_FALLBACK_COLORS[token];
|
|
102
|
+
const fontSize = (step) => (props) => props?.theme?.fontSizes?.[step] ?? FONT_SIZE_FALLBACK[step];
|
|
103
|
+
const isDarkTheme = (themeObject) => themeObject?.colorScheme === "dark";
|
|
104
|
+
const byScheme = (light, dark) => (props) => isDarkTheme(props?.theme) ? dark : light;
|
|
105
|
+
const brand = Object.freeze({
|
|
106
|
+
primary: "#0369A1",
|
|
107
|
+
primaryHover: "#075985",
|
|
108
|
+
secondary: "#9333EA",
|
|
109
|
+
secondaryHover: "#7E22CE",
|
|
110
|
+
success: "#15803D",
|
|
111
|
+
successHover: "#166534",
|
|
112
|
+
warning: "#B45309",
|
|
113
|
+
warningHover: "#92400E",
|
|
114
|
+
danger: "#DC2626",
|
|
115
|
+
dangerHover: "#B91C1C",
|
|
116
|
+
gradient: "linear-gradient(135deg, #0369A1 0%, #9333EA 100%)",
|
|
117
|
+
gradientHover: "linear-gradient(135deg, #075985 0%, #7E22CE 100%)"
|
|
118
|
+
});
|
|
119
|
+
const ACCENT_VALUES = Object.freeze({
|
|
120
|
+
primary: ["#0369A1", "#7DD3FC"],
|
|
121
|
+
secondary: ["#7E22CE", "#D8B4FE"],
|
|
122
|
+
success: ["#15803D", "#86EFAC"],
|
|
123
|
+
warning: ["#B45309", "#FCD34D"],
|
|
124
|
+
danger: ["#B91C1C", "#FCA5A5"]
|
|
125
|
+
});
|
|
126
|
+
const accent = Object.freeze(
|
|
127
|
+
Object.fromEntries(
|
|
128
|
+
Object.entries(ACCENT_VALUES).map(([name, [light, dark]]) => [name, byScheme(light, dark)])
|
|
129
|
+
)
|
|
130
|
+
);
|
|
131
|
+
const theme = {
|
|
132
|
+
shadows: {
|
|
133
|
+
sm: "0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1)",
|
|
134
|
+
md: "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1)",
|
|
135
|
+
lg: "0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1)",
|
|
136
|
+
xl: "0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1)"
|
|
137
|
+
},
|
|
138
|
+
transitions: {
|
|
139
|
+
fast: "150ms cubic-bezier(0.4, 0, 0.2, 1)",
|
|
140
|
+
normal: "300ms cubic-bezier(0.4, 0, 0.2, 1)",
|
|
141
|
+
slow: "500ms cubic-bezier(0.4, 0, 0.2, 1)"
|
|
142
|
+
},
|
|
143
|
+
spacing: {
|
|
144
|
+
xs: "4px",
|
|
145
|
+
sm: "8px",
|
|
146
|
+
md: "16px",
|
|
147
|
+
lg: "24px",
|
|
148
|
+
xl: "32px",
|
|
149
|
+
"2xl": "48px"
|
|
150
|
+
},
|
|
151
|
+
borderRadius: {
|
|
152
|
+
md: "8px",
|
|
153
|
+
lg: "12px",
|
|
154
|
+
xl: "16px"
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
const reducedMotion = css`
|
|
158
|
+
@media (prefers-reduced-motion: reduce) {
|
|
159
|
+
animation: none !important;
|
|
160
|
+
transition: none !important;
|
|
161
|
+
|
|
162
|
+
&::before,
|
|
163
|
+
&::after {
|
|
164
|
+
animation: none !important;
|
|
165
|
+
transition: none !important;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
&:hover,
|
|
169
|
+
&:active,
|
|
170
|
+
&:focus-visible {
|
|
171
|
+
transform: none !important;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
`;
|
|
175
|
+
const motionSafe = (block) => css`
|
|
176
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
177
|
+
${block}
|
|
178
|
+
}
|
|
179
|
+
`;
|
|
180
|
+
const CONTROL_HEIGHT = "36px";
|
|
181
|
+
const CTA_HEIGHT = "52px";
|
|
182
|
+
const disabledState = css`
|
|
183
|
+
&:disabled,
|
|
184
|
+
&[aria-disabled='true'] {
|
|
185
|
+
opacity: 0.6;
|
|
186
|
+
cursor: not-allowed;
|
|
187
|
+
}
|
|
188
|
+
`;
|
|
189
|
+
const textButtonBase = css`
|
|
190
|
+
font-weight: 600;
|
|
191
|
+
padding: 8px 16px;
|
|
192
|
+
min-height: ${CONTROL_HEIGHT};
|
|
193
|
+
transition: all 0.2s ease;
|
|
194
|
+
${disabledState}
|
|
195
|
+
${reducedMotion}
|
|
196
|
+
`;
|
|
197
|
+
const GradientButton = styled(Button)`
|
|
198
|
+
&& {
|
|
199
|
+
${textButtonBase}
|
|
200
|
+
background: ${brand.gradient};
|
|
201
|
+
color: #ffffff;
|
|
202
|
+
border: none;
|
|
203
|
+
|
|
204
|
+
&:hover:not(:disabled) {
|
|
205
|
+
background: ${brand.gradientHover};
|
|
206
|
+
transform: translateY(-1px);
|
|
207
|
+
box-shadow: 0 4px 12px rgba(3, 105, 161, 0.3);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
&:active:not(:disabled) {
|
|
211
|
+
transform: translateY(0);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
`;
|
|
215
|
+
const SecondaryButton = styled(Button)`
|
|
216
|
+
&& {
|
|
217
|
+
${textButtonBase}
|
|
218
|
+
background: ${color("neutral0")};
|
|
219
|
+
color: ${accent.secondary};
|
|
220
|
+
border: 2px solid transparent;
|
|
221
|
+
background-image:
|
|
222
|
+
linear-gradient(${color("neutral0")}, ${color("neutral0")}), ${brand.gradient};
|
|
223
|
+
background-origin: border-box;
|
|
224
|
+
background-clip: padding-box, border-box;
|
|
225
|
+
|
|
226
|
+
&:hover:not(:disabled) {
|
|
227
|
+
background: ${brand.gradient};
|
|
228
|
+
background-clip: padding-box;
|
|
229
|
+
color: #ffffff;
|
|
230
|
+
transform: translateY(-1px);
|
|
231
|
+
box-shadow: 0 4px 12px rgba(147, 51, 234, 0.3);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
&:active:not(:disabled) {
|
|
235
|
+
transform: translateY(0);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
`;
|
|
239
|
+
const TertiaryButton = styled(Button)`
|
|
240
|
+
&& {
|
|
241
|
+
${textButtonBase}
|
|
242
|
+
font-weight: 500;
|
|
243
|
+
background: transparent;
|
|
244
|
+
color: ${color("neutral700")};
|
|
245
|
+
border: 1px solid ${color("neutral200")};
|
|
246
|
+
|
|
247
|
+
&:hover:not(:disabled) {
|
|
248
|
+
background: ${color("neutral100")};
|
|
249
|
+
border-color: ${color("neutral300")};
|
|
250
|
+
color: ${color("neutral800")};
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
`;
|
|
254
|
+
const DangerButton = styled(Button)`
|
|
255
|
+
&& {
|
|
256
|
+
${textButtonBase}
|
|
257
|
+
background: rgba(220, 38, 38, 0.12);
|
|
258
|
+
color: ${accent.danger};
|
|
259
|
+
border: 1px solid rgba(220, 38, 38, 0.3);
|
|
260
|
+
|
|
261
|
+
&:hover:not(:disabled) {
|
|
262
|
+
background: ${brand.danger};
|
|
263
|
+
color: #ffffff;
|
|
264
|
+
border-color: ${brand.danger};
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
`;
|
|
268
|
+
const iconButtonBase = css`
|
|
269
|
+
padding: 8px;
|
|
270
|
+
min-width: ${CONTROL_HEIGHT};
|
|
271
|
+
min-height: ${CONTROL_HEIGHT};
|
|
272
|
+
width: ${CONTROL_HEIGHT};
|
|
273
|
+
height: ${CONTROL_HEIGHT};
|
|
274
|
+
border-radius: 10px;
|
|
275
|
+
display: flex;
|
|
276
|
+
align-items: center;
|
|
277
|
+
justify-content: center;
|
|
278
|
+
transition: all 0.2s ease;
|
|
279
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
|
|
280
|
+
|
|
281
|
+
svg {
|
|
282
|
+
width: 18px;
|
|
283
|
+
height: 18px;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
&:hover:not(:disabled) {
|
|
287
|
+
color: #ffffff;
|
|
288
|
+
transform: translateY(-1px);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
${disabledState}
|
|
292
|
+
${reducedMotion}
|
|
293
|
+
`;
|
|
294
|
+
const createIconButton = ({ tint, fg, hover, hoverBorder }) => {
|
|
295
|
+
const StyledIconButton = styled(Button)`
|
|
296
|
+
&& {
|
|
297
|
+
${iconButtonBase}
|
|
298
|
+
background: linear-gradient(135deg, rgba(${tint}, 0.06) 0%, rgba(${tint}, 0.14) 100%);
|
|
299
|
+
color: ${fg};
|
|
300
|
+
border: 1px solid rgba(${tint}, 0.32);
|
|
301
|
+
|
|
302
|
+
&:hover:not(:disabled) {
|
|
303
|
+
background: ${hover};
|
|
304
|
+
border-color: ${hoverBorder};
|
|
305
|
+
box-shadow: 0 4px 12px rgba(${tint}, 0.3);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
`;
|
|
309
|
+
const IconOnlyButton = forwardRef(({ title, ...props }, ref) => {
|
|
310
|
+
const label = props["aria-label"] ?? title;
|
|
311
|
+
const button = /* @__PURE__ */ jsx(StyledIconButton, { ref, "aria-label": label, ...props });
|
|
312
|
+
return label ? /* @__PURE__ */ jsx(Tooltip, { label, children: button }) : button;
|
|
313
|
+
});
|
|
314
|
+
return IconOnlyButton;
|
|
315
|
+
};
|
|
316
|
+
const IconButton = createIconButton({
|
|
317
|
+
tint: "128, 128, 128",
|
|
318
|
+
fg: color("neutral600"),
|
|
319
|
+
hover: `linear-gradient(135deg, ${brand.primary} 0%, ${brand.primaryHover} 100%)`,
|
|
320
|
+
hoverBorder: brand.primary
|
|
321
|
+
});
|
|
322
|
+
IconButton.displayName = "IconButton";
|
|
323
|
+
const IconButtonDanger = createIconButton({
|
|
324
|
+
tint: "220, 38, 38",
|
|
325
|
+
fg: accent.danger,
|
|
326
|
+
hover: `linear-gradient(135deg, ${brand.danger} 0%, ${brand.dangerHover} 100%)`,
|
|
327
|
+
hoverBorder: brand.danger
|
|
328
|
+
});
|
|
329
|
+
IconButtonDanger.displayName = "IconButtonDanger";
|
|
330
|
+
const IconButtonPrimary = createIconButton({
|
|
331
|
+
tint: "3, 105, 161",
|
|
332
|
+
fg: accent.primary,
|
|
333
|
+
hover: `linear-gradient(135deg, ${brand.primary} 0%, ${brand.primaryHover} 100%)`,
|
|
334
|
+
hoverBorder: brand.primary
|
|
335
|
+
});
|
|
336
|
+
IconButtonPrimary.displayName = "IconButtonPrimary";
|
|
337
|
+
const IconButtonSuccess = createIconButton({
|
|
338
|
+
tint: "21, 128, 61",
|
|
339
|
+
fg: accent.success,
|
|
340
|
+
hover: `linear-gradient(135deg, ${brand.success} 0%, ${brand.successHover} 100%)`,
|
|
341
|
+
hoverBorder: brand.success
|
|
342
|
+
});
|
|
343
|
+
IconButtonSuccess.displayName = "IconButtonSuccess";
|
|
344
|
+
const IconButtonWarning = createIconButton({
|
|
345
|
+
tint: "180, 83, 9",
|
|
346
|
+
fg: accent.warning,
|
|
347
|
+
hover: `linear-gradient(135deg, ${brand.warning} 0%, ${brand.warningHover} 100%)`,
|
|
348
|
+
hoverBorder: brand.warning
|
|
349
|
+
});
|
|
350
|
+
IconButtonWarning.displayName = "IconButtonWarning";
|
|
351
|
+
const IconButtonPurple = createIconButton({
|
|
352
|
+
tint: "147, 51, 234",
|
|
353
|
+
fg: accent.secondary,
|
|
354
|
+
hover: `linear-gradient(135deg, ${brand.secondary} 0%, ${brand.secondaryHover} 100%)`,
|
|
355
|
+
hoverBorder: brand.secondary
|
|
356
|
+
});
|
|
357
|
+
IconButtonPurple.displayName = "IconButtonPurple";
|
|
358
|
+
const CTAButton = styled(Button)`
|
|
359
|
+
&& {
|
|
360
|
+
background: ${brand.gradient};
|
|
361
|
+
color: #ffffff;
|
|
362
|
+
font-weight: 700;
|
|
363
|
+
font-size: ${fontSize(3)};
|
|
364
|
+
border: none;
|
|
365
|
+
padding: 14px 28px;
|
|
366
|
+
min-height: ${CTA_HEIGHT};
|
|
367
|
+
border-radius: 12px;
|
|
368
|
+
transition: all 0.2s ease;
|
|
369
|
+
box-shadow: 0 4px 14px rgba(3, 105, 161, 0.25);
|
|
370
|
+
|
|
371
|
+
&:hover:not(:disabled) {
|
|
372
|
+
background: ${brand.gradientHover};
|
|
373
|
+
transform: translateY(-2px);
|
|
374
|
+
box-shadow: 0 6px 20px rgba(3, 105, 161, 0.35);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
&:active:not(:disabled) {
|
|
378
|
+
transform: translateY(0);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
${disabledState}
|
|
382
|
+
${reducedMotion}
|
|
383
|
+
}
|
|
384
|
+
`;
|
|
385
|
+
const useReturnFocus = (isOpen, getFallback) => {
|
|
386
|
+
const openerRef = useRef(null);
|
|
387
|
+
const fallbackRef = useRef(getFallback);
|
|
388
|
+
useLayoutEffect(() => {
|
|
389
|
+
fallbackRef.current = getFallback;
|
|
390
|
+
}, [getFallback]);
|
|
391
|
+
useLayoutEffect(() => {
|
|
392
|
+
if (!isOpen) return void 0;
|
|
393
|
+
const active = document.activeElement;
|
|
394
|
+
const opener = active && active !== document.body ? active : null;
|
|
395
|
+
openerRef.current = opener;
|
|
396
|
+
return () => {
|
|
397
|
+
window.setTimeout(() => {
|
|
398
|
+
const current = document.activeElement;
|
|
399
|
+
if (current && current !== document.body) return;
|
|
400
|
+
const target = opener?.isConnected ? opener : fallbackRef.current?.();
|
|
401
|
+
target?.focus?.();
|
|
402
|
+
}, 0);
|
|
403
|
+
};
|
|
404
|
+
}, [isOpen]);
|
|
405
|
+
return useCallback((event) => {
|
|
406
|
+
const opener = openerRef.current;
|
|
407
|
+
openerRef.current = null;
|
|
408
|
+
const target = opener?.isConnected ? opener : fallbackRef.current?.();
|
|
409
|
+
if (!target || typeof target.focus !== "function") return;
|
|
410
|
+
event.preventDefault();
|
|
411
|
+
target.focus();
|
|
412
|
+
}, []);
|
|
413
|
+
};
|
|
414
|
+
const WarningIcon = styled(ExclamationTriangleIcon)`
|
|
415
|
+
width: 24px;
|
|
416
|
+
height: 24px;
|
|
417
|
+
color: ${(p) => p.$danger ? accent.danger(p) : accent.warning(p)};
|
|
418
|
+
`;
|
|
419
|
+
const ConfirmDialog = ({
|
|
420
|
+
isOpen,
|
|
421
|
+
onClose,
|
|
422
|
+
onConfirm,
|
|
423
|
+
title,
|
|
424
|
+
description,
|
|
425
|
+
children,
|
|
426
|
+
confirmLabel,
|
|
427
|
+
cancelLabel,
|
|
428
|
+
variant = "danger",
|
|
429
|
+
isLoading = false,
|
|
430
|
+
confirmIcon,
|
|
431
|
+
getReturnFocusFallback
|
|
432
|
+
}) => {
|
|
433
|
+
const { t } = useMagicMailIntl();
|
|
434
|
+
const [isPending, setIsPending] = useState(false);
|
|
435
|
+
const mounted = useRef(true);
|
|
436
|
+
const returnFocus = useReturnFocus(Boolean(isOpen), getReturnFocusFallback);
|
|
437
|
+
useEffect(() => {
|
|
438
|
+
mounted.current = true;
|
|
439
|
+
return () => {
|
|
440
|
+
mounted.current = false;
|
|
441
|
+
};
|
|
442
|
+
}, []);
|
|
443
|
+
const busy = isLoading || isPending;
|
|
444
|
+
const danger = variant === "danger";
|
|
445
|
+
const ConfirmButton = danger ? DangerButton : GradientButton;
|
|
446
|
+
const handleConfirm = async () => {
|
|
447
|
+
if (busy) return;
|
|
448
|
+
const result = onConfirm?.();
|
|
449
|
+
if (result && typeof result.then === "function") {
|
|
450
|
+
setIsPending(true);
|
|
451
|
+
try {
|
|
452
|
+
await result;
|
|
453
|
+
} catch {
|
|
454
|
+
} finally {
|
|
455
|
+
if (mounted.current) setIsPending(false);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
const handleOpenChange = (open) => {
|
|
460
|
+
if (!open && !busy) onClose?.();
|
|
461
|
+
};
|
|
462
|
+
return /* @__PURE__ */ jsx(Dialog.Root, { open: Boolean(isOpen), onOpenChange: handleOpenChange, children: /* @__PURE__ */ jsxs(Dialog.Content, { onCloseAutoFocus: returnFocus, children: [
|
|
463
|
+
/* @__PURE__ */ jsx(Dialog.Header, { textAlign: "center", children: title }),
|
|
464
|
+
/* @__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: [
|
|
465
|
+
description ? /* @__PURE__ */ jsx(Dialog.Description, { textAlign: "center", children: description }) : null,
|
|
466
|
+
typeof children === "string" ? /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", textAlign: "center", children }) : children
|
|
467
|
+
] }) }),
|
|
468
|
+
/* @__PURE__ */ jsxs(Dialog.Footer, { children: [
|
|
469
|
+
/* @__PURE__ */ jsx(Dialog.Cancel, { children: /* @__PURE__ */ jsx(TertiaryButton, { fullWidth: true, disabled: busy, children: cancelLabel ?? t("common.cancel", "Cancel") }) }),
|
|
470
|
+
/* @__PURE__ */ jsx(
|
|
471
|
+
ConfirmButton,
|
|
472
|
+
{
|
|
473
|
+
fullWidth: true,
|
|
474
|
+
onClick: handleConfirm,
|
|
475
|
+
loading: busy,
|
|
476
|
+
startIcon: busy ? void 0 : confirmIcon,
|
|
477
|
+
children: confirmLabel ?? (danger ? t("common.delete", "Delete") : t("common.confirm", "Confirm"))
|
|
478
|
+
}
|
|
479
|
+
)
|
|
480
|
+
] })
|
|
481
|
+
] }) });
|
|
482
|
+
};
|
|
483
|
+
const UnsavedChangesDialog = ({ guard, title, description, confirmLabel }) => {
|
|
484
|
+
const { t } = useMagicMailIntl();
|
|
485
|
+
return /* @__PURE__ */ jsx(
|
|
486
|
+
ConfirmDialog,
|
|
487
|
+
{
|
|
488
|
+
isOpen: guard.isOpen,
|
|
489
|
+
onClose: guard.cancel,
|
|
490
|
+
onConfirm: guard.confirm,
|
|
491
|
+
title: title ?? t("common.unsaved.title", "Discard unsaved changes?"),
|
|
492
|
+
description: description ?? t(
|
|
493
|
+
"common.unsaved.description",
|
|
494
|
+
"You have unsaved changes. If you continue, they will be lost."
|
|
495
|
+
),
|
|
496
|
+
confirmLabel: confirmLabel ?? t("common.unsaved.confirm", "Discard changes"),
|
|
497
|
+
cancelLabel: t("common.unsaved.cancel", "Keep editing")
|
|
498
|
+
}
|
|
499
|
+
);
|
|
500
|
+
};
|
|
501
|
+
const shouldBlockNavigation = (isDirty, transition = {}) => {
|
|
502
|
+
if (!isDirty) return false;
|
|
503
|
+
const { currentLocation, nextLocation } = transition;
|
|
504
|
+
if (!nextLocation) return false;
|
|
505
|
+
return currentLocation?.pathname !== nextLocation.pathname || (currentLocation?.search ?? "") !== (nextLocation.search ?? "");
|
|
506
|
+
};
|
|
507
|
+
const attachBeforeUnload = (active, win = typeof window === "undefined" ? void 0 : window) => {
|
|
508
|
+
if (!active || typeof win?.addEventListener !== "function") return () => {
|
|
509
|
+
};
|
|
510
|
+
const handler = (event) => {
|
|
511
|
+
event.preventDefault();
|
|
512
|
+
event.returnValue = "";
|
|
513
|
+
return "";
|
|
514
|
+
};
|
|
515
|
+
win.addEventListener("beforeunload", handler);
|
|
516
|
+
return () => win.removeEventListener("beforeunload", handler);
|
|
517
|
+
};
|
|
518
|
+
const useDiscardState = (isDirty) => {
|
|
519
|
+
const dirtyRef = useRef(Boolean(isDirty));
|
|
520
|
+
useLayoutEffect(() => {
|
|
521
|
+
dirtyRef.current = Boolean(isDirty);
|
|
522
|
+
}, [isDirty]);
|
|
523
|
+
const [pendingAction, setPendingAction] = useState(null);
|
|
524
|
+
const runGuarded = useCallback((action) => {
|
|
525
|
+
if (typeof action !== "function") return;
|
|
526
|
+
if (dirtyRef.current) {
|
|
527
|
+
setPendingAction(() => action);
|
|
528
|
+
} else {
|
|
529
|
+
action();
|
|
530
|
+
}
|
|
531
|
+
}, []);
|
|
532
|
+
return { dirtyRef, pendingAction, setPendingAction, runGuarded };
|
|
533
|
+
};
|
|
534
|
+
const useDiscardGuard = (isDirty) => {
|
|
535
|
+
const { pendingAction, setPendingAction, runGuarded } = useDiscardState(isDirty);
|
|
536
|
+
const confirm = useCallback(() => {
|
|
537
|
+
const action = pendingAction;
|
|
538
|
+
setPendingAction(null);
|
|
539
|
+
if (action) action();
|
|
540
|
+
}, [pendingAction, setPendingAction]);
|
|
541
|
+
const cancel = useCallback(() => setPendingAction(null), [setPendingAction]);
|
|
542
|
+
return { isDirty: Boolean(isDirty), isOpen: pendingAction !== null, confirm, cancel, runGuarded };
|
|
543
|
+
};
|
|
544
|
+
const useUnsavedChangesGuard = (isDirty) => {
|
|
545
|
+
const { dirtyRef, pendingAction, setPendingAction, runGuarded } = useDiscardState(isDirty);
|
|
546
|
+
const bypassRef = useRef(false);
|
|
547
|
+
const blocker = useBlocker(
|
|
548
|
+
useCallback(
|
|
549
|
+
(transition) => !bypassRef.current && shouldBlockNavigation(dirtyRef.current, transition),
|
|
550
|
+
[dirtyRef]
|
|
551
|
+
)
|
|
552
|
+
);
|
|
553
|
+
useEffect(() => attachBeforeUnload(Boolean(isDirty)), [isDirty]);
|
|
554
|
+
useEffect(() => {
|
|
555
|
+
if (blocker.state === "blocked" && !isDirty) blocker.proceed();
|
|
556
|
+
}, [blocker, isDirty]);
|
|
557
|
+
const confirm = useCallback(() => {
|
|
558
|
+
if (blocker.state === "blocked") blocker.proceed();
|
|
559
|
+
const action = pendingAction;
|
|
560
|
+
setPendingAction(null);
|
|
561
|
+
if (action) action();
|
|
562
|
+
}, [blocker, pendingAction, setPendingAction]);
|
|
563
|
+
const cancel = useCallback(() => {
|
|
564
|
+
if (blocker.state === "blocked") blocker.reset();
|
|
565
|
+
setPendingAction(null);
|
|
566
|
+
}, [blocker, setPendingAction]);
|
|
567
|
+
const bypass = useCallback((action) => {
|
|
568
|
+
bypassRef.current = true;
|
|
569
|
+
try {
|
|
570
|
+
return action();
|
|
571
|
+
} finally {
|
|
572
|
+
bypassRef.current = false;
|
|
573
|
+
}
|
|
574
|
+
}, []);
|
|
575
|
+
return {
|
|
576
|
+
isDirty: Boolean(isDirty),
|
|
577
|
+
isOpen: blocker.state === "blocked" || pendingAction !== null,
|
|
578
|
+
confirm,
|
|
579
|
+
cancel,
|
|
580
|
+
runGuarded,
|
|
581
|
+
bypass
|
|
582
|
+
};
|
|
583
|
+
};
|
|
584
|
+
const MAX_EMAIL_LENGTH = 254;
|
|
585
|
+
const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
586
|
+
function isValidEmailAddress(value) {
|
|
587
|
+
return typeof value === "string" && value.length <= MAX_EMAIL_LENGTH && value === value.trim() && EMAIL_PATTERN.test(value);
|
|
588
|
+
}
|
|
589
|
+
function isOptionalEmailAddressValid(value) {
|
|
590
|
+
return typeof value === "string" && (value.trim() === "" || isValidEmailAddress(value));
|
|
591
|
+
}
|
|
592
|
+
const PluginPageTitle = ({ children }) => {
|
|
593
|
+
const { t } = useMagicMailIntl();
|
|
594
|
+
return /* @__PURE__ */ jsx(Page.Title, { children: t("common.pageTitle", "{page} | MagicMail", { page: children }) });
|
|
595
|
+
};
|
|
596
|
+
const SectionHeading = styled(Typography).attrs((props) => ({
|
|
597
|
+
tag: props.tag ?? "h2",
|
|
598
|
+
variant: props.variant ?? "delta",
|
|
599
|
+
textColor: props.textColor ?? "neutral700"
|
|
600
|
+
}))`
|
|
601
|
+
&& {
|
|
602
|
+
font-size: 1.5rem;
|
|
603
|
+
font-weight: 600;
|
|
604
|
+
}
|
|
605
|
+
`;
|
|
606
|
+
styled(Typography).attrs((props) => ({
|
|
607
|
+
tag: props.tag ?? "h3",
|
|
608
|
+
variant: props.variant ?? "delta",
|
|
609
|
+
textColor: props.textColor ?? "neutral800"
|
|
610
|
+
}))``;
|
|
611
|
+
const LOGS_PAGE_SIZE = 25;
|
|
612
|
+
const MAX_SEARCH_LENGTH = 500;
|
|
613
|
+
const MAX_CLEAR_ROUNDS = 100;
|
|
614
|
+
const normalizeSearch = (value) => typeof value === "string" ? value.trim().slice(0, MAX_SEARCH_LENGTH) : "";
|
|
615
|
+
const buildEmailLogsUrl = ({ page = 1, pageSize = LOGS_PAGE_SIZE, search = "" } = {}) => {
|
|
616
|
+
const params = new URLSearchParams();
|
|
617
|
+
const safePage = Number.isInteger(page) && page > 0 ? page : 1;
|
|
618
|
+
const safeSize = Number.isInteger(pageSize) && pageSize > 0 ? Math.min(pageSize, 100) : LOGS_PAGE_SIZE;
|
|
619
|
+
params.set("page", String(safePage));
|
|
620
|
+
params.set("pageSize", String(safeSize));
|
|
621
|
+
const term = normalizeSearch(search);
|
|
622
|
+
if (term) params.set("search", term);
|
|
623
|
+
return `/magic-mail/analytics/emails?${params.toString()}`;
|
|
624
|
+
};
|
|
625
|
+
const clampPage = (page, pageCount) => {
|
|
626
|
+
const max = Number.isInteger(pageCount) && pageCount > 0 ? pageCount : 1;
|
|
627
|
+
if (!Number.isInteger(page) || page < 1) return 1;
|
|
628
|
+
return Math.min(page, max);
|
|
629
|
+
};
|
|
630
|
+
const clearAllEmailLogsInBatches = async (del, { maxRounds = MAX_CLEAR_ROUNDS } = {}) => {
|
|
631
|
+
let deletedCount = 0;
|
|
632
|
+
for (let round = 0; round < maxRounds; round += 1) {
|
|
633
|
+
const response = await del("/magic-mail/analytics/emails");
|
|
634
|
+
const data = response?.data ?? {};
|
|
635
|
+
const deleted = Number.isFinite(Number(data.deletedCount)) ? Number(data.deletedCount) : 0;
|
|
636
|
+
deletedCount += deleted;
|
|
637
|
+
if (data.hasMore !== true) return { deletedCount, complete: true };
|
|
638
|
+
if (deleted <= 0) return { deletedCount, complete: false };
|
|
639
|
+
}
|
|
640
|
+
return { deletedCount, complete: false };
|
|
641
|
+
};
|
|
642
|
+
const apiErrorMessage = (error) => {
|
|
643
|
+
const message = error?.response?.data?.error?.message ?? error?.response?.data?.message;
|
|
644
|
+
return typeof message === "string" && message.trim() ? message.trim() : null;
|
|
645
|
+
};
|
|
646
|
+
export {
|
|
647
|
+
clampPage as A,
|
|
648
|
+
apiErrorMessage as B,
|
|
649
|
+
ConfirmDialog as C,
|
|
650
|
+
DangerButton as D,
|
|
651
|
+
clearAllEmailLogsInBatches as E,
|
|
652
|
+
GradientButton as G,
|
|
653
|
+
IconButton as I,
|
|
654
|
+
LOGS_PAGE_SIZE as L,
|
|
655
|
+
PluginPageTitle as P,
|
|
656
|
+
SectionHeading as S,
|
|
657
|
+
TertiaryButton as T,
|
|
658
|
+
UnsavedChangesDialog as U,
|
|
659
|
+
isValidEmailAddress as a,
|
|
660
|
+
byScheme as b,
|
|
661
|
+
color as c,
|
|
662
|
+
useReturnFocus as d,
|
|
663
|
+
useDiscardGuard as e,
|
|
664
|
+
fontSize as f,
|
|
665
|
+
brand as g,
|
|
666
|
+
accent as h,
|
|
667
|
+
isOptionalEmailAddressValid as i,
|
|
668
|
+
useMagicMailRBAC as j,
|
|
669
|
+
CTAButton as k,
|
|
670
|
+
IconButtonPrimary as l,
|
|
671
|
+
motionSafe as m,
|
|
672
|
+
IconButtonDanger as n,
|
|
673
|
+
CTA_HEIGHT as o,
|
|
674
|
+
SecondaryButton as p,
|
|
675
|
+
IconButtonPurple as q,
|
|
676
|
+
reducedMotion as r,
|
|
677
|
+
IconButtonSuccess as s,
|
|
678
|
+
theme as t,
|
|
679
|
+
useMagicMailIntl as u,
|
|
680
|
+
CONTROL_HEIGHT as v,
|
|
681
|
+
useUnsavedChangesGuard as w,
|
|
682
|
+
isDarkTheme as x,
|
|
683
|
+
normalizeSearch as y,
|
|
684
|
+
buildEmailLogsUrl as z
|
|
685
|
+
};
|