strapi-plugin-magic-mail 2.0.2 → 2.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -17
- package/admin/src/index.js +26 -25
- package/admin/src/pages/Analytics.jsx +19 -19
- package/admin/src/pages/EmailDesigner/EditorPage.jsx +33 -33
- package/admin/src/pages/EmailDesigner/TemplateList.jsx +36 -36
- package/admin/src/pages/HomePage.jsx +25 -88
- package/admin/src/pages/LicensePage.jsx +12 -6
- package/admin/src/pages/RoutingRules.jsx +21 -21
- package/admin/src/pages/Settings.jsx +3 -3
- package/admin/src/utils/theme.js +85 -0
- package/dist/_chunks/{App-DYNCyt54.mjs → App-BZaHrE0R.mjs} +184 -200
- package/dist/_chunks/{App-DF9vAGXX.js → App-Bze8Ixs_.js} +184 -200
- package/dist/_chunks/{LicensePage-CJXwPnEe.js → LicensePage-Bg72gy8w.js} +12 -6
- package/dist/_chunks/{LicensePage-Bl02myMx.mjs → LicensePage-ndUhjynY.mjs} +12 -6
- package/dist/_chunks/{Settings-zuFQ3pnn.js → Settings-BSFLpt0H.js} +3 -7
- package/dist/_chunks/{Settings-C_TmKwcz.mjs → Settings-Ca5UE3c1.mjs} +3 -7
- package/dist/admin/index.js +41 -24
- package/dist/admin/index.mjs +41 -24
- package/dist/server/index.js +602 -665
- package/dist/server/index.mjs +602 -665
- package/package.json +1 -1
- package/server/src/bootstrap.js +16 -16
- package/server/src/config/features.js +7 -7
- package/server/src/controllers/accounts.js +15 -6
- package/server/src/controllers/analytics.js +56 -42
- package/server/src/controllers/license.js +9 -7
- package/server/src/controllers/oauth.js +9 -9
- package/server/src/controllers/routing-rules.js +18 -11
- package/server/src/controllers/test.js +111 -193
- package/server/src/services/account-manager.js +73 -21
- package/server/src/services/analytics.js +88 -72
- package/server/src/services/email-designer.js +131 -284
- package/server/src/services/email-router.js +69 -43
- package/server/src/services/license-guard.js +24 -24
- package/server/src/services/oauth.js +11 -11
- package/server/src/services/service.js +1 -1
- package/server/src/utils/encryption.js +1 -1
|
@@ -32,7 +32,7 @@ const Title = styled__default.default(designSystem.Typography)`
|
|
|
32
32
|
`;
|
|
33
33
|
const Subtitle = styled__default.default(designSystem.Typography)`
|
|
34
34
|
font-size: 1.125rem;
|
|
35
|
-
color:
|
|
35
|
+
color: ${(props) => props.theme.colors.neutral600};
|
|
36
36
|
line-height: 1.6;
|
|
37
37
|
display: block;
|
|
38
38
|
`;
|
|
@@ -42,20 +42,25 @@ const TierGrid = styled__default.default(designSystem.Flex)`
|
|
|
42
42
|
max-width: 1080px;
|
|
43
43
|
justify-content: center;
|
|
44
44
|
flex-wrap: wrap;
|
|
45
|
+
align-items: stretch;
|
|
45
46
|
`;
|
|
46
47
|
const TierWrapper = styled__default.default(designSystem.Box)`
|
|
47
48
|
flex: 1;
|
|
48
49
|
min-width: 280px;
|
|
49
50
|
max-width: 340px;
|
|
51
|
+
display: flex;
|
|
50
52
|
`;
|
|
51
53
|
const TierCard = styled__default.default(designSystem.Box)`
|
|
52
|
-
background:
|
|
54
|
+
background: ${(props) => props.theme.colors.neutral0};
|
|
53
55
|
border-radius: 16px;
|
|
54
56
|
padding: 32px;
|
|
55
|
-
border: 2px solid ${(props) => props.$featured ? "#0EA5E9" :
|
|
57
|
+
border: 2px solid ${(props) => props.$featured ? "#0EA5E9" : props.theme.colors.neutral200};
|
|
56
58
|
position: relative;
|
|
57
59
|
transition: all 0.3s ease;
|
|
58
60
|
box-shadow: ${(props) => props.$featured ? "0 20px 25px -5px rgba(14, 165, 233, 0.25), 0 8px 10px -6px rgba(14, 165, 233, 0.2)" : "0 10px 15px -3px rgba(15, 23, 42, 0.08), 0 4px 6px -4px rgba(15, 23, 42, 0.05)"};
|
|
61
|
+
display: flex;
|
|
62
|
+
flex-direction: column;
|
|
63
|
+
width: 100%;
|
|
59
64
|
|
|
60
65
|
&:hover {
|
|
61
66
|
transform: translateY(-4px);
|
|
@@ -99,11 +104,12 @@ const TierPrice = styled__default.default(designSystem.Typography)`
|
|
|
99
104
|
margin-bottom: 4px;
|
|
100
105
|
`;
|
|
101
106
|
const TierDescription = styled__default.default(designSystem.Typography)`
|
|
102
|
-
color:
|
|
107
|
+
color: ${(props) => props.theme.colors.neutral600};
|
|
103
108
|
margin-bottom: 24px;
|
|
104
109
|
`;
|
|
105
110
|
const FeatureList = styled__default.default(designSystem.Box)`
|
|
106
111
|
margin-bottom: 24px;
|
|
112
|
+
flex: 1;
|
|
107
113
|
`;
|
|
108
114
|
const Feature = styled__default.default(designSystem.Flex)`
|
|
109
115
|
gap: 12px;
|
|
@@ -148,8 +154,8 @@ const CurrentPlanBadge = styled__default.default(designSystem.Badge)`
|
|
|
148
154
|
display: flex;
|
|
149
155
|
align-items: center;
|
|
150
156
|
justify-content: center;
|
|
151
|
-
background:
|
|
152
|
-
color:
|
|
157
|
+
background: ${(props) => props.theme.colors.neutral100};
|
|
158
|
+
color: ${(props) => props.theme.colors.neutral600};
|
|
153
159
|
font-weight: 600;
|
|
154
160
|
font-size: 15px;
|
|
155
161
|
`;
|
|
@@ -28,7 +28,7 @@ const Title = styled(Typography)`
|
|
|
28
28
|
`;
|
|
29
29
|
const Subtitle = styled(Typography)`
|
|
30
30
|
font-size: 1.125rem;
|
|
31
|
-
color:
|
|
31
|
+
color: ${(props) => props.theme.colors.neutral600};
|
|
32
32
|
line-height: 1.6;
|
|
33
33
|
display: block;
|
|
34
34
|
`;
|
|
@@ -38,20 +38,25 @@ const TierGrid = styled(Flex)`
|
|
|
38
38
|
max-width: 1080px;
|
|
39
39
|
justify-content: center;
|
|
40
40
|
flex-wrap: wrap;
|
|
41
|
+
align-items: stretch;
|
|
41
42
|
`;
|
|
42
43
|
const TierWrapper = styled(Box)`
|
|
43
44
|
flex: 1;
|
|
44
45
|
min-width: 280px;
|
|
45
46
|
max-width: 340px;
|
|
47
|
+
display: flex;
|
|
46
48
|
`;
|
|
47
49
|
const TierCard = styled(Box)`
|
|
48
|
-
background:
|
|
50
|
+
background: ${(props) => props.theme.colors.neutral0};
|
|
49
51
|
border-radius: 16px;
|
|
50
52
|
padding: 32px;
|
|
51
|
-
border: 2px solid ${(props) => props.$featured ? "#0EA5E9" :
|
|
53
|
+
border: 2px solid ${(props) => props.$featured ? "#0EA5E9" : props.theme.colors.neutral200};
|
|
52
54
|
position: relative;
|
|
53
55
|
transition: all 0.3s ease;
|
|
54
56
|
box-shadow: ${(props) => props.$featured ? "0 20px 25px -5px rgba(14, 165, 233, 0.25), 0 8px 10px -6px rgba(14, 165, 233, 0.2)" : "0 10px 15px -3px rgba(15, 23, 42, 0.08), 0 4px 6px -4px rgba(15, 23, 42, 0.05)"};
|
|
57
|
+
display: flex;
|
|
58
|
+
flex-direction: column;
|
|
59
|
+
width: 100%;
|
|
55
60
|
|
|
56
61
|
&:hover {
|
|
57
62
|
transform: translateY(-4px);
|
|
@@ -95,11 +100,12 @@ const TierPrice = styled(Typography)`
|
|
|
95
100
|
margin-bottom: 4px;
|
|
96
101
|
`;
|
|
97
102
|
const TierDescription = styled(Typography)`
|
|
98
|
-
color:
|
|
103
|
+
color: ${(props) => props.theme.colors.neutral600};
|
|
99
104
|
margin-bottom: 24px;
|
|
100
105
|
`;
|
|
101
106
|
const FeatureList = styled(Box)`
|
|
102
107
|
margin-bottom: 24px;
|
|
108
|
+
flex: 1;
|
|
103
109
|
`;
|
|
104
110
|
const Feature = styled(Flex)`
|
|
105
111
|
gap: 12px;
|
|
@@ -144,8 +150,8 @@ const CurrentPlanBadge = styled(Badge)`
|
|
|
144
150
|
display: flex;
|
|
145
151
|
align-items: center;
|
|
146
152
|
justify-content: center;
|
|
147
|
-
background:
|
|
148
|
-
color:
|
|
153
|
+
background: ${(props) => props.theme.colors.neutral100};
|
|
154
|
+
color: ${(props) => props.theme.colors.neutral600};
|
|
149
155
|
font-weight: 600;
|
|
150
156
|
font-size: 15px;
|
|
151
157
|
`;
|
|
@@ -9,10 +9,6 @@ const styled = require("styled-components");
|
|
|
9
9
|
const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
|
|
10
10
|
const styled__default = /* @__PURE__ */ _interopDefault(styled);
|
|
11
11
|
const theme = {
|
|
12
|
-
colors: {
|
|
13
|
-
neutral: { 200: "#E5E7EB" }
|
|
14
|
-
},
|
|
15
|
-
shadows: { sm: "0 1px 3px rgba(0,0,0,0.1)" },
|
|
16
12
|
borderRadius: { lg: "12px" }
|
|
17
13
|
};
|
|
18
14
|
const fadeIn = styled.keyframes`
|
|
@@ -32,9 +28,9 @@ const StickySaveBar = styled__default.default(designSystem.Box)`
|
|
|
32
28
|
position: sticky;
|
|
33
29
|
top: 0;
|
|
34
30
|
z-index: 10;
|
|
35
|
-
background:
|
|
36
|
-
border-bottom: 1px solid ${theme.colors.
|
|
37
|
-
box-shadow:
|
|
31
|
+
background: ${(props) => props.theme.colors.neutral0};
|
|
32
|
+
border-bottom: 1px solid ${(props) => props.theme.colors.neutral200};
|
|
33
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
38
34
|
`;
|
|
39
35
|
const LicenseKeyBanner = styled__default.default(designSystem.Box)`
|
|
40
36
|
background: linear-gradient(135deg, #0EA5E9 0%, #A855F7 100%);
|
|
@@ -5,10 +5,6 @@ import { useFetchClient, useNotification } from "@strapi/strapi/admin";
|
|
|
5
5
|
import { ArrowPathIcon, DocumentDuplicateIcon, ArrowDownTrayIcon, UserIcon, ShieldCheckIcon, SparklesIcon, ChartBarIcon } from "@heroicons/react/24/outline";
|
|
6
6
|
import styled, { css, keyframes } from "styled-components";
|
|
7
7
|
const theme = {
|
|
8
|
-
colors: {
|
|
9
|
-
neutral: { 200: "#E5E7EB" }
|
|
10
|
-
},
|
|
11
|
-
shadows: { sm: "0 1px 3px rgba(0,0,0,0.1)" },
|
|
12
8
|
borderRadius: { lg: "12px" }
|
|
13
9
|
};
|
|
14
10
|
const fadeIn = keyframes`
|
|
@@ -28,9 +24,9 @@ const StickySaveBar = styled(Box)`
|
|
|
28
24
|
position: sticky;
|
|
29
25
|
top: 0;
|
|
30
26
|
z-index: 10;
|
|
31
|
-
background:
|
|
32
|
-
border-bottom: 1px solid ${theme.colors.
|
|
33
|
-
box-shadow:
|
|
27
|
+
background: ${(props) => props.theme.colors.neutral0};
|
|
28
|
+
border-bottom: 1px solid ${(props) => props.theme.colors.neutral200};
|
|
29
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
34
30
|
`;
|
|
35
31
|
const LicenseKeyBanner = styled(Box)`
|
|
36
32
|
background: linear-gradient(135deg, #0EA5E9 0%, #A855F7 100%);
|
package/dist/admin/index.js
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
const React = require("react");
|
|
3
3
|
const jsxRuntime = require("react/jsx-runtime");
|
|
4
4
|
const outline = require("@heroicons/react/24/outline");
|
|
5
|
+
const __variableDynamicImportRuntimeHelper = (glob, path, segs) => {
|
|
6
|
+
const v = glob[path];
|
|
7
|
+
if (v) {
|
|
8
|
+
return typeof v === "function" ? v() : Promise.resolve(v);
|
|
9
|
+
}
|
|
10
|
+
return new Promise((_, reject) => {
|
|
11
|
+
(typeof queueMicrotask === "function" ? queueMicrotask : setTimeout)(
|
|
12
|
+
reject.bind(
|
|
13
|
+
null,
|
|
14
|
+
new Error(
|
|
15
|
+
"Unknown variable dynamic import: " + path + (path.split("/").length !== segs ? ". Note that variables only represent file names one level deep." : "")
|
|
16
|
+
)
|
|
17
|
+
)
|
|
18
|
+
);
|
|
19
|
+
});
|
|
20
|
+
};
|
|
5
21
|
const strapi = { "name": "magic-mail" };
|
|
6
22
|
const pluginPkg = {
|
|
7
23
|
strapi
|
|
@@ -16,6 +32,13 @@ const Initializer = ({ setPlugin }) => {
|
|
|
16
32
|
};
|
|
17
33
|
const PluginIcon = () => /* @__PURE__ */ jsxRuntime.jsx(outline.EnvelopeIcon, { style: { width: 24, height: 24 } });
|
|
18
34
|
const name = pluginPkg.strapi.name;
|
|
35
|
+
const prefixPluginTranslations = (data, pluginId2) => {
|
|
36
|
+
const prefixed = {};
|
|
37
|
+
Object.keys(data).forEach((key) => {
|
|
38
|
+
prefixed[`${pluginId2}.${key}`] = data[key];
|
|
39
|
+
});
|
|
40
|
+
return prefixed;
|
|
41
|
+
};
|
|
19
42
|
const index = {
|
|
20
43
|
register(app) {
|
|
21
44
|
app.addMenuLink({
|
|
@@ -25,7 +48,7 @@ const index = {
|
|
|
25
48
|
id: `${pluginId}.plugin.name`,
|
|
26
49
|
defaultMessage: "MagicMail"
|
|
27
50
|
},
|
|
28
|
-
Component: () => Promise.resolve().then(() => require("../_chunks/App-
|
|
51
|
+
Component: () => Promise.resolve().then(() => require("../_chunks/App-Bze8Ixs_.js"))
|
|
29
52
|
});
|
|
30
53
|
app.createSettingSection(
|
|
31
54
|
{
|
|
@@ -41,7 +64,7 @@ const index = {
|
|
|
41
64
|
},
|
|
42
65
|
id: "upgrade",
|
|
43
66
|
to: `/settings/${pluginId}/upgrade`,
|
|
44
|
-
Component: () => Promise.resolve().then(() => require("../_chunks/LicensePage-
|
|
67
|
+
Component: () => Promise.resolve().then(() => require("../_chunks/LicensePage-Bg72gy8w.js"))
|
|
45
68
|
},
|
|
46
69
|
{
|
|
47
70
|
intlLabel: {
|
|
@@ -50,7 +73,7 @@ const index = {
|
|
|
50
73
|
},
|
|
51
74
|
id: "license",
|
|
52
75
|
to: `/settings/${pluginId}/license`,
|
|
53
|
-
Component: () => Promise.resolve().then(() => require("../_chunks/Settings-
|
|
76
|
+
Component: () => Promise.resolve().then(() => require("../_chunks/Settings-BSFLpt0H.js"))
|
|
54
77
|
}
|
|
55
78
|
]
|
|
56
79
|
);
|
|
@@ -62,28 +85,22 @@ const index = {
|
|
|
62
85
|
});
|
|
63
86
|
},
|
|
64
87
|
async registerTrads({ locales }) {
|
|
65
|
-
const importedTrads =
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
locale: language
|
|
80
|
-
})).catch(() => ({
|
|
81
|
-
data: {},
|
|
82
|
-
locale: language
|
|
83
|
-
}))
|
|
84
|
-
)
|
|
88
|
+
const importedTrads = await Promise.all(
|
|
89
|
+
locales.map((locale) => {
|
|
90
|
+
return __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({ "./translations/de.json": () => Promise.resolve().then(() => require("../_chunks/de-CN-G9j1S.js")), "./translations/en.json": () => Promise.resolve().then(() => require("../_chunks/en-BDc7Jk8u.js")), "./translations/es.json": () => Promise.resolve().then(() => require("../_chunks/es-BpV1MIdm.js")), "./translations/fr.json": () => Promise.resolve().then(() => require("../_chunks/fr-vpziIpRp.js")), "./translations/pt.json": () => Promise.resolve().then(() => require("../_chunks/pt-ODpAhDNa.js")) }), `./translations/${locale}.json`, 3).then(({ default: data }) => {
|
|
91
|
+
return {
|
|
92
|
+
data: prefixPluginTranslations(data, pluginId),
|
|
93
|
+
locale
|
|
94
|
+
};
|
|
95
|
+
}).catch(() => {
|
|
96
|
+
return {
|
|
97
|
+
data: {},
|
|
98
|
+
locale
|
|
99
|
+
};
|
|
100
|
+
});
|
|
101
|
+
})
|
|
85
102
|
);
|
|
86
|
-
return Promise.resolve(
|
|
103
|
+
return Promise.resolve(importedTrads);
|
|
87
104
|
}
|
|
88
105
|
};
|
|
89
106
|
module.exports = index;
|
package/dist/admin/index.mjs
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
import { useRef, useEffect } from "react";
|
|
2
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
3
|
import { EnvelopeIcon } from "@heroicons/react/24/outline";
|
|
4
|
+
const __variableDynamicImportRuntimeHelper = (glob, path, segs) => {
|
|
5
|
+
const v = glob[path];
|
|
6
|
+
if (v) {
|
|
7
|
+
return typeof v === "function" ? v() : Promise.resolve(v);
|
|
8
|
+
}
|
|
9
|
+
return new Promise((_, reject) => {
|
|
10
|
+
(typeof queueMicrotask === "function" ? queueMicrotask : setTimeout)(
|
|
11
|
+
reject.bind(
|
|
12
|
+
null,
|
|
13
|
+
new Error(
|
|
14
|
+
"Unknown variable dynamic import: " + path + (path.split("/").length !== segs ? ". Note that variables only represent file names one level deep." : "")
|
|
15
|
+
)
|
|
16
|
+
)
|
|
17
|
+
);
|
|
18
|
+
});
|
|
19
|
+
};
|
|
4
20
|
const strapi = { "name": "magic-mail" };
|
|
5
21
|
const pluginPkg = {
|
|
6
22
|
strapi
|
|
@@ -15,6 +31,13 @@ const Initializer = ({ setPlugin }) => {
|
|
|
15
31
|
};
|
|
16
32
|
const PluginIcon = () => /* @__PURE__ */ jsx(EnvelopeIcon, { style: { width: 24, height: 24 } });
|
|
17
33
|
const name = pluginPkg.strapi.name;
|
|
34
|
+
const prefixPluginTranslations = (data, pluginId2) => {
|
|
35
|
+
const prefixed = {};
|
|
36
|
+
Object.keys(data).forEach((key) => {
|
|
37
|
+
prefixed[`${pluginId2}.${key}`] = data[key];
|
|
38
|
+
});
|
|
39
|
+
return prefixed;
|
|
40
|
+
};
|
|
18
41
|
const index = {
|
|
19
42
|
register(app) {
|
|
20
43
|
app.addMenuLink({
|
|
@@ -24,7 +47,7 @@ const index = {
|
|
|
24
47
|
id: `${pluginId}.plugin.name`,
|
|
25
48
|
defaultMessage: "MagicMail"
|
|
26
49
|
},
|
|
27
|
-
Component: () => import("../_chunks/App-
|
|
50
|
+
Component: () => import("../_chunks/App-BZaHrE0R.mjs")
|
|
28
51
|
});
|
|
29
52
|
app.createSettingSection(
|
|
30
53
|
{
|
|
@@ -40,7 +63,7 @@ const index = {
|
|
|
40
63
|
},
|
|
41
64
|
id: "upgrade",
|
|
42
65
|
to: `/settings/${pluginId}/upgrade`,
|
|
43
|
-
Component: () => import("../_chunks/LicensePage-
|
|
66
|
+
Component: () => import("../_chunks/LicensePage-ndUhjynY.mjs")
|
|
44
67
|
},
|
|
45
68
|
{
|
|
46
69
|
intlLabel: {
|
|
@@ -49,7 +72,7 @@ const index = {
|
|
|
49
72
|
},
|
|
50
73
|
id: "license",
|
|
51
74
|
to: `/settings/${pluginId}/license`,
|
|
52
|
-
Component: () => import("../_chunks/Settings-
|
|
75
|
+
Component: () => import("../_chunks/Settings-Ca5UE3c1.mjs")
|
|
53
76
|
}
|
|
54
77
|
]
|
|
55
78
|
);
|
|
@@ -61,28 +84,22 @@ const index = {
|
|
|
61
84
|
});
|
|
62
85
|
},
|
|
63
86
|
async registerTrads({ locales }) {
|
|
64
|
-
const importedTrads =
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
locale: language
|
|
79
|
-
})).catch(() => ({
|
|
80
|
-
data: {},
|
|
81
|
-
locale: language
|
|
82
|
-
}))
|
|
83
|
-
)
|
|
87
|
+
const importedTrads = await Promise.all(
|
|
88
|
+
locales.map((locale) => {
|
|
89
|
+
return __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({ "./translations/de.json": () => import("../_chunks/de-DS04rP54.mjs"), "./translations/en.json": () => import("../_chunks/en-BEFQJXvR.mjs"), "./translations/es.json": () => import("../_chunks/es-DQHwzPpP.mjs"), "./translations/fr.json": () => import("../_chunks/fr-BG1WfEVm.mjs"), "./translations/pt.json": () => import("../_chunks/pt-CMoGrOib.mjs") }), `./translations/${locale}.json`, 3).then(({ default: data }) => {
|
|
90
|
+
return {
|
|
91
|
+
data: prefixPluginTranslations(data, pluginId),
|
|
92
|
+
locale
|
|
93
|
+
};
|
|
94
|
+
}).catch(() => {
|
|
95
|
+
return {
|
|
96
|
+
data: {},
|
|
97
|
+
locale
|
|
98
|
+
};
|
|
99
|
+
});
|
|
100
|
+
})
|
|
84
101
|
);
|
|
85
|
-
return Promise.resolve(
|
|
102
|
+
return Promise.resolve(importedTrads);
|
|
86
103
|
}
|
|
87
104
|
};
|
|
88
105
|
export {
|