@aglyn/plugins-email 1.0.0-beta.143

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 (144) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +7 -0
  3. package/package.json +58 -0
  4. package/src/index.d.ts +35 -0
  5. package/src/index.js +35 -0
  6. package/src/index.js.map +1 -0
  7. package/src/lib/components/campaign-design-create-widget.d.ts +21 -0
  8. package/src/lib/components/campaign-design-create-widget.js +61 -0
  9. package/src/lib/components/campaign-design-create-widget.js.map +1 -0
  10. package/src/lib/components/campaign-sender-editor-widget.d.ts +18 -0
  11. package/src/lib/components/campaign-sender-editor-widget.js +39 -0
  12. package/src/lib/components/campaign-sender-editor-widget.js.map +1 -0
  13. package/src/lib/components/campaign-topic-options-widget.d.ts +19 -0
  14. package/src/lib/components/campaign-topic-options-widget.js +48 -0
  15. package/src/lib/components/campaign-topic-options-widget.js.map +1 -0
  16. package/src/lib/components/campaign-topic-select.d.ts +32 -0
  17. package/src/lib/components/campaign-topic-select.js +87 -0
  18. package/src/lib/components/campaign-topic-select.js.map +1 -0
  19. package/src/lib/components/dynamic-list-rule-fields.d.ts +173 -0
  20. package/src/lib/components/dynamic-list-rule-fields.js +1473 -0
  21. package/src/lib/components/dynamic-list-rule-fields.js.map +1 -0
  22. package/src/lib/components/email-blocks.d.ts +111 -0
  23. package/src/lib/components/email-blocks.js +875 -0
  24. package/src/lib/components/email-blocks.js.map +1 -0
  25. package/src/lib/components/email-design-preview.d.ts +62 -0
  26. package/src/lib/components/email-design-preview.js +174 -0
  27. package/src/lib/components/email-design-preview.js.map +1 -0
  28. package/src/lib/components/email-screens-card.d.ts +42 -0
  29. package/src/lib/components/email-screens-card.js +277 -0
  30. package/src/lib/components/email-screens-card.js.map +1 -0
  31. package/src/lib/components/email-template-detail.d.ts +48 -0
  32. package/src/lib/components/email-template-detail.js +681 -0
  33. package/src/lib/components/email-template-detail.js.map +1 -0
  34. package/src/lib/components/email-topic-detail.d.ts +32 -0
  35. package/src/lib/components/email-topic-detail.js +293 -0
  36. package/src/lib/components/email-topic-detail.js.map +1 -0
  37. package/src/lib/components/email-topics-card.d.ts +46 -0
  38. package/src/lib/components/email-topics-card.js +327 -0
  39. package/src/lib/components/email-topics-card.js.map +1 -0
  40. package/src/lib/components/email-zones.d.ts +28 -0
  41. package/src/lib/components/email-zones.js +20 -0
  42. package/src/lib/components/email-zones.js.map +1 -0
  43. package/src/lib/components/emails-console-page.d.ts +32 -0
  44. package/src/lib/components/emails-console-page.js +229 -0
  45. package/src/lib/components/emails-console-page.js.map +1 -0
  46. package/src/lib/components/emails-console-sections.d.ts +36 -0
  47. package/src/lib/components/emails-console-sections.js +108 -0
  48. package/src/lib/components/emails-console-sections.js.map +1 -0
  49. package/src/lib/components/list-detail-card.d.ts +47 -0
  50. package/src/lib/components/list-detail-card.js +273 -0
  51. package/src/lib/components/list-detail-card.js.map +1 -0
  52. package/src/lib/components/list-edit-card.d.ts +11 -0
  53. package/src/lib/components/list-edit-card.js +287 -0
  54. package/src/lib/components/list-edit-card.js.map +1 -0
  55. package/src/lib/components/list-import-drawer.d.ts +22 -0
  56. package/src/lib/components/list-import-drawer.js +662 -0
  57. package/src/lib/components/list-import-drawer.js.map +1 -0
  58. package/src/lib/components/list-members-panel.d.ts +94 -0
  59. package/src/lib/components/list-members-panel.js +686 -0
  60. package/src/lib/components/list-members-panel.js.map +1 -0
  61. package/src/lib/components/lists-card.d.ts +28 -0
  62. package/src/lib/components/lists-card.js +377 -0
  63. package/src/lib/components/lists-card.js.map +1 -0
  64. package/src/lib/components/sending-domain-detail.d.ts +26 -0
  65. package/src/lib/components/sending-domain-detail.js +496 -0
  66. package/src/lib/components/sending-domain-detail.js.map +1 -0
  67. package/src/lib/components/sending-domains-card.d.ts +33 -0
  68. package/src/lib/components/sending-domains-card.js +962 -0
  69. package/src/lib/components/sending-domains-card.js.map +1 -0
  70. package/src/lib/components/sending-sender-drawer.d.ts +94 -0
  71. package/src/lib/components/sending-sender-drawer.js +543 -0
  72. package/src/lib/components/sending-sender-drawer.js.map +1 -0
  73. package/src/lib/components/suppressions-card.d.ts +49 -0
  74. package/src/lib/components/suppressions-card.js +639 -0
  75. package/src/lib/components/suppressions-card.js.map +1 -0
  76. package/src/lib/components/use-org-email-topics.d.ts +79 -0
  77. package/src/lib/components/use-org-email-topics.js +111 -0
  78. package/src/lib/components/use-org-email-topics.js.map +1 -0
  79. package/src/lib/constants/bundle-common.d.ts +18 -0
  80. package/src/lib/constants/bundle-common.js +18 -0
  81. package/src/lib/constants/bundle-common.js.map +1 -0
  82. package/src/lib/hooks/use-org-company-options.d.ts +20 -0
  83. package/src/lib/hooks/use-org-company-options.js +138 -0
  84. package/src/lib/hooks/use-org-company-options.js.map +1 -0
  85. package/src/lib/hooks/use-org-contact-fields.d.ts +40 -0
  86. package/src/lib/hooks/use-org-contact-fields.js +91 -0
  87. package/src/lib/hooks/use-org-contact-fields.js.map +1 -0
  88. package/src/lib/hooks/use-org-contact-segments.d.ts +16 -0
  89. package/src/lib/hooks/use-org-contact-segments.js +55 -0
  90. package/src/lib/hooks/use-org-contact-segments.js.map +1 -0
  91. package/src/lib/hooks/use-org-crm-views.d.ts +8 -0
  92. package/src/lib/hooks/use-org-crm-views.js +74 -0
  93. package/src/lib/hooks/use-org-crm-views.js.map +1 -0
  94. package/src/lib/hooks/use-org-lists.d.ts +8 -0
  95. package/src/lib/hooks/use-org-lists.js +47 -0
  96. package/src/lib/hooks/use-org-lists.js.map +1 -0
  97. package/src/lib/model/email-design-document.d.ts +52 -0
  98. package/src/lib/model/email-design-document.js +62 -0
  99. package/src/lib/model/email-design-document.js.map +1 -0
  100. package/src/lib/model/index.d.ts +64 -0
  101. package/src/lib/model/index.js +71 -0
  102. package/src/lib/model/index.js.map +1 -0
  103. package/src/lib/model/sending-domain-status.d.ts +99 -0
  104. package/src/lib/model/sending-domain-status.js +196 -0
  105. package/src/lib/model/sending-domain-status.js.map +1 -0
  106. package/src/lib/model/template-provenance.d.ts +113 -0
  107. package/src/lib/model/template-provenance.js +107 -0
  108. package/src/lib/model/template-provenance.js.map +1 -0
  109. package/src/lib/model/template-report.d.ts +158 -0
  110. package/src/lib/model/template-report.js +249 -0
  111. package/src/lib/model/template-report.js.map +1 -0
  112. package/src/lib/plugin.d.ts +27 -0
  113. package/src/lib/plugin.js +163 -0
  114. package/src/lib/plugin.js.map +1 -0
  115. package/src/lib/server-console.d.ts +116 -0
  116. package/src/lib/server-console.js +422 -0
  117. package/src/lib/server-console.js.map +1 -0
  118. package/src/lib/server-email-drafts.d.ts +104 -0
  119. package/src/lib/server-email-drafts.js +381 -0
  120. package/src/lib/server-email-drafts.js.map +1 -0
  121. package/src/lib/server-list-gate.d.ts +183 -0
  122. package/src/lib/server-list-gate.js +365 -0
  123. package/src/lib/server-list-gate.js.map +1 -0
  124. package/src/lib/server-list-import.d.ts +199 -0
  125. package/src/lib/server-list-import.js +632 -0
  126. package/src/lib/server-list-import.js.map +1 -0
  127. package/src/lib/server-suppressions.d.ts +135 -0
  128. package/src/lib/server-suppressions.js +295 -0
  129. package/src/lib/server-suppressions.js.map +1 -0
  130. package/src/lib/server.d.ts +19 -0
  131. package/src/lib/server.js +834 -0
  132. package/src/lib/server.js.map +1 -0
  133. package/src/lib/site.d.ts +26 -0
  134. package/src/lib/site.js +81 -0
  135. package/src/lib/site.js.map +1 -0
  136. package/src/lib/unsubscribe-link.d.ts +311 -0
  137. package/src/lib/unsubscribe-link.js +398 -0
  138. package/src/lib/unsubscribe-link.js.map +1 -0
  139. package/src/lib/utils/create-email-screen.d.ts +59 -0
  140. package/src/lib/utils/create-email-screen.js +59 -0
  141. package/src/lib/utils/create-email-screen.js.map +1 -0
  142. package/src/lib/utils/generate-preset-id.d.ts +19 -0
  143. package/src/lib/utils/generate-preset-id.js +25 -0
  144. package/src/lib/utils/generate-preset-id.js.map +1 -0
@@ -0,0 +1,962 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ 'use client';
17
+ import { _ as _extends } from "@swc/helpers/_/_extends";
18
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
19
+ import { pluginDocsHelp } from "@aglyn/aglyn";
20
+ import { ICON_VARIANT_CLOSE } from "@aglyn/shared-data-enums";
21
+ import { mdiDeleteOutline, mdiDnsOutline, mdiEmailCheckOutline, mdiRefresh } from "@aglyn/shared-data-mdi";
22
+ import { CardDisplay, Container, HelpTip, MdiIcon, SrOnly, useConfirmationContext } from "@aglyn/shared-ui-jsx";
23
+ import { NavigationDrawerComponent } from "@aglyn/shared-ui-jsx/components/navigation-drawer.component";
24
+ import RowActionsMenu from "@aglyn/shared-ui-jsx/components/row-actions-menu.component";
25
+ import { ScrollTable } from "@aglyn/shared-ui-jsx/components/scroll-table.component";
26
+ import { useSnackbar } from "@aglyn/shared-ui-snackstack";
27
+ import { Alert, Button, Chip, Divider, IconButton, Stack, TableBody, TableCell, TableHead, TableRow, TextField, Typography } from "@mui/material";
28
+ import { useCallback, useEffect, useRef, useState } from "react";
29
+ import { useRouter } from "next/navigation";
30
+ import { describeSendingDomain, describeSendingDomainRemoval, INCONCLUSIVE_CHECK } from "../model/sending-domain-status.js";
31
+ import SendingSenderDrawer from "./sending-sender-drawer.js";
32
+ import { useSendingApi } from "@aglyn/tenant-feature-instance/hooks/use-sending-identity-api";
33
+ /**
34
+ * WHERE A WORKSPACE PROVES IT OWNS THE DOMAIN ITS MAIL LEAVES ON.
35
+ *
36
+ * ## The two halves on one screen, and why they are not one control
37
+ *
38
+ * A domain is proved by the ORG — publishing a DKIM record is a statement
39
+ * about a zone, and an agency running four sites on `client.com` should make
40
+ * it once. Which of the proved domains THIS SITE sends as is a separate,
41
+ * per-site choice, because the `From:` a recipient sees belongs to the site.
42
+ *
43
+ * So the top of this card says what this site sends as right now, and the
44
+ * table below is the org's set of domains. Collapsing them would make one of
45
+ * the two questions unanswerable: with one control per site, four sites would
46
+ * publish the same record four times; with one control per org, an agency
47
+ * could not give two clients two different `From:` lines.
48
+ *
49
+ * ## Every state gets its own sentence
50
+ *
51
+ * Five situations, and `describeSendingDomain` holds four of them — the fifth,
52
+ * a check nobody answered, is deliberately not a state at all. It rides the
53
+ * surface as a transient notice beside whatever the record still says, which
54
+ * is the whole reason the model file exists.
55
+ */ export function SendingDomainsCard(props) {
56
+ var _ref, _ref1, _ref2, _ref3;
57
+ var _view_refusal, _view_dedicated;
58
+ const { hostId, basePath } = props;
59
+ const call = useSendingApi();
60
+ const router = useRouter();
61
+ const { confirm } = useConfirmationContext();
62
+ const { enqueueSnackbar } = useSnackbar();
63
+ /*
64
+ * The snackbar held in a REF, for the reason `use-campaign-send-api` holds
65
+ * the user in one: `load` is a dependency of the effect that runs it, so
66
+ * anything whose identity changes per render re-runs the fetch — and a
67
+ * hook returning a fresh object each render turns that into a loop that
68
+ * never settles. Reading it through a ref makes the effect depend on the
69
+ * things that actually identify the request.
70
+ */ const notifyRef = useRef(enqueueSnackbar);
71
+ notifyRef.current = enqueueSnackbar;
72
+ const [view, setView] = useState(null);
73
+ const [loading, setLoading] = useState(true);
74
+ const [adding, setAdding] = useState(false);
75
+ /**
76
+ * The open sender editor, and which row it is on.
77
+ *
78
+ * One state rather than an `open` flag beside an id, so the drawer cannot be
79
+ * open with no answer to "which sender" — the id and the openness are the
80
+ * same fact. `senderId: null` is the add case.
81
+ */ const [senderEditor, setSenderEditor] = useState(null);
82
+ /** The row a list action is in flight for, so only its buttons go quiet. */ const [senderBusy, setSenderBusy] = useState('');
83
+ /** The same, for the domains table below it. */ const [domainBusy, setDomainBusy] = useState('');
84
+ const [addingBusy, setAddingBusy] = useState(false);
85
+ const [newDomain, setNewDomain] = useState('');
86
+ const [addError, setAddError] = useState('');
87
+ const [claimingDedicated, setClaimingDedicated] = useState(false);
88
+ const load = useCallback(async ()=>{
89
+ setLoading(true);
90
+ const { response, payload } = await call({
91
+ path: 'sending-identity',
92
+ method: 'GET',
93
+ query: {
94
+ hostId
95
+ }
96
+ });
97
+ setLoading(false);
98
+ if (!response.ok) {
99
+ var _ref;
100
+ /*
101
+ * The refusal itself, not a generic failure. A reader without the org
102
+ * admin role, or on a plan that does not carry white-label, needs to
103
+ * know which of those it is — "something went wrong" would send them
104
+ * to support for an answer the response already gave.
105
+ */ setView(null);
106
+ return void notifyRef.current((_ref = payload == null ? void 0 : payload.error) != null ? _ref : 'Could not read the sending identity', {
107
+ variant: 'warning'
108
+ });
109
+ }
110
+ setView(payload);
111
+ }, [
112
+ call,
113
+ hostId
114
+ ]);
115
+ useEffect(()=>{
116
+ void load();
117
+ }, [
118
+ load
119
+ ]);
120
+ /**
121
+ * A DOMAIN'S OWN PAGE, BUILT IN ONE PLACE.
122
+ *
123
+ * Three things navigate here — the row, the row's menu, and the redirect
124
+ * after adding a domain — and the `sending/` segment is the whole address:
125
+ * `${basePath}/{domain}` resolves to no section and renders an empty page,
126
+ * which is the worst possible landing for all three, because every one of
127
+ * them happens at the moment somebody needs the DNS records. Derived once
128
+ * so a second copy cannot be written without it.
129
+ */ const domainPath = useCallback((domain)=>`${basePath}/sending/${encodeURIComponent(domain)}`, [
130
+ basePath
131
+ ]);
132
+ /**
133
+ * The list's own two actions — move the default, and remove a sender.
134
+ *
135
+ * Both are a POST and a re-read rather than a local edit of `view`. The
136
+ * server decides which row is the default and what the projection onto the
137
+ * host document becomes, so re-reading is what keeps this list and the
138
+ * address a send would actually resolve to one answer.
139
+ */ const handleSenderAction = useCallback(async (action, senderId)=>{
140
+ setSenderBusy(senderId);
141
+ const { response, payload } = await call({
142
+ path: 'sending-identity',
143
+ method: 'POST',
144
+ body: {
145
+ hostId,
146
+ action,
147
+ senderId
148
+ }
149
+ });
150
+ setSenderBusy('');
151
+ if (!response.ok) {
152
+ var _ref;
153
+ return void notifyRef.current((_ref = payload == null ? void 0 : payload.error) != null ? _ref : 'Could not update that sender', {
154
+ variant: 'warning'
155
+ });
156
+ }
157
+ void load();
158
+ }, [
159
+ call,
160
+ hostId,
161
+ load
162
+ ]);
163
+ /*
164
+ * THE ROW'S OWN ACTIONS.
165
+ *
166
+ * Each one posts to the route that owns the decision rather than to a route
167
+ * that happens to be nearby: checking DNS and releasing a claim are facts
168
+ * about the ORG's domain, and which of them this SITE sends as is a per-site
169
+ * selection. That split is the reason there are two routes, and a menu that
170
+ * collapsed it would be the surface deciding an agency's client sends as
171
+ * another client's name.
172
+ *
173
+ * All three re-read afterwards instead of patching `view`. The server
174
+ * decides what the identity resolves to, and a list that edited itself from
175
+ * a response body would be the second opinion this card exists not to be.
176
+ */ const handleVerifyDomain = useCallback(async (domain)=>{
177
+ if (!(view == null ? void 0 : view.orgId) || domainBusy) return;
178
+ setDomainBusy(domain);
179
+ const { response, payload } = await call({
180
+ path: 'sending-domains',
181
+ method: 'POST',
182
+ body: {
183
+ orgId: view.orgId,
184
+ domain,
185
+ action: 'verify'
186
+ }
187
+ });
188
+ setDomainBusy('');
189
+ /*
190
+ * THE THIRD OUTCOME, held here exactly as the domain's own page holds
191
+ * it. A `503` means the lookup got no answer, so nothing about the
192
+ * record changed; reporting it as a failed check is how a customer
193
+ * whose DNS is perfect ends up editing a zone that has nothing wrong
194
+ * with it.
195
+ */ if (response.status === 503) {
196
+ return void notifyRef.current(INCONCLUSIVE_CHECK.text, {
197
+ variant: 'info'
198
+ });
199
+ }
200
+ if (!response.ok) {
201
+ var _ref;
202
+ return void notifyRef.current((_ref = payload == null ? void 0 : payload.error) != null ? _ref : 'The check could not run', {
203
+ variant: 'warning'
204
+ });
205
+ }
206
+ await load();
207
+ notifyRef.current((payload == null ? void 0 : payload.verified) ? `Verified — ${domain} can send` : `Checked. Some of ${domain}’s records are still missing.`, {
208
+ variant: (payload == null ? void 0 : payload.verified) ? 'success' : 'info'
209
+ });
210
+ }, [
211
+ call,
212
+ view == null ? void 0 : view.orgId,
213
+ domainBusy,
214
+ load
215
+ ]);
216
+ const handleUseDomain = useCallback(async (domain)=>{
217
+ if (domainBusy) return;
218
+ setDomainBusy(domain);
219
+ const { response, payload } = await call({
220
+ path: 'sending-identity',
221
+ method: 'POST',
222
+ body: {
223
+ hostId,
224
+ domain
225
+ }
226
+ });
227
+ setDomainBusy('');
228
+ if (!response.ok) {
229
+ var _ref;
230
+ return void notifyRef.current((_ref = payload == null ? void 0 : payload.error) != null ? _ref : 'Could not change the identity', {
231
+ variant: 'warning'
232
+ });
233
+ }
234
+ await load();
235
+ // The address the ROUTE settled on, not the one this click asked for —
236
+ // only the response knows what the `From:` line now says.
237
+ notifyRef.current((payload == null ? void 0 : payload.from) ? `This site now sends as ${payload.from}` : 'This site’s sending address was changed', {
238
+ variant: 'success'
239
+ });
240
+ }, [
241
+ call,
242
+ hostId,
243
+ domainBusy,
244
+ load
245
+ ]);
246
+ const handleReleaseDomain = useCallback(async (domain)=>{
247
+ if (!(view == null ? void 0 : view.orgId) || domainBusy) return;
248
+ const ok = await confirm(_extends({}, describeSendingDomainRemoval({
249
+ domain,
250
+ selected: view.selected,
251
+ platformDomain: view.platformDomain
252
+ }), {
253
+ confirmationButtonProps: {
254
+ color: 'error'
255
+ }
256
+ }))// `confirm` resolves with no value and REJECTS on cancel, so the
257
+ // resolved value alone can never gate this.
258
+ .then(()=>true).catch(()=>false);
259
+ if (!ok) return;
260
+ setDomainBusy(domain);
261
+ const { response, payload } = await call({
262
+ path: 'sending-domains',
263
+ method: 'DELETE',
264
+ query: {
265
+ orgId: view.orgId,
266
+ domain
267
+ }
268
+ });
269
+ setDomainBusy('');
270
+ if (!response.ok) {
271
+ var _ref;
272
+ return void notifyRef.current((_ref = payload == null ? void 0 : payload.error) != null ? _ref : 'Could not remove the domain', {
273
+ variant: 'warning'
274
+ });
275
+ }
276
+ await load();
277
+ }, [
278
+ call,
279
+ confirm,
280
+ domainBusy,
281
+ view == null ? void 0 : view.orgId,
282
+ view == null ? void 0 : view.selected,
283
+ view == null ? void 0 : view.platformDomain,
284
+ load
285
+ ]);
286
+ const handleAdd = useCallback(async ()=>{
287
+ var _ref;
288
+ const domain = newDomain.trim();
289
+ if (!domain || addingBusy) return;
290
+ setAddError('');
291
+ setAddingBusy(true);
292
+ const { response, payload } = await call({
293
+ path: 'sending-domains',
294
+ method: 'POST',
295
+ body: {
296
+ orgId: view == null ? void 0 : view.orgId,
297
+ domain,
298
+ action: 'request'
299
+ }
300
+ });
301
+ setAddingBusy(false);
302
+ if (!response.ok) {
303
+ var _ref1;
304
+ setAddError((_ref1 = payload == null ? void 0 : payload.error) != null ? _ref1 : 'Could not add that domain');
305
+ return;
306
+ }
307
+ setAdding(false);
308
+ setNewDomain('');
309
+ /*
310
+ * Straight to the domain's own page, because the thing the person came
311
+ * for is now there: the records to publish. A list row saying
312
+ * "Publish the records" with the records one click further away would
313
+ * make the next step something they have to go looking for.
314
+ */ router.push(domainPath((_ref = payload == null ? void 0 : payload.domain) != null ? _ref : domain));
315
+ }, [
316
+ call,
317
+ view == null ? void 0 : view.orgId,
318
+ domainPath,
319
+ router,
320
+ newDomain,
321
+ addingBusy
322
+ ]);
323
+ /**
324
+ * ASK FOR THE PLATFORM SUBDOMAIN.
325
+ *
326
+ * The second of the two ways out of the marketing refusal, and the one for a
327
+ * merchant who cannot publish DNS — an agency's client on a domain somebody
328
+ * else administers, or a site whose registrar access is a support ticket
329
+ * away. It is offered rather than issued because it spends a provider domain
330
+ * slot and three records in Aglyn's own zone, where a domain the merchant
331
+ * owns spends neither.
332
+ *
333
+ * Reloads rather than patching the view from the response. The claim moves
334
+ * three things at once — the site's selection, the org's domain list and the
335
+ * identity the send path resolves — and a surface that updated one of them
336
+ * from a response body would be the second opinion this card exists not to
337
+ * be.
338
+ */ const handleClaimDedicated = useCallback(async ()=>{
339
+ if (claimingDedicated) return;
340
+ setClaimingDedicated(true);
341
+ const { response, payload } = await call({
342
+ path: 'sending-identity',
343
+ method: 'POST',
344
+ body: {
345
+ hostId,
346
+ action: 'request-dedicated'
347
+ }
348
+ });
349
+ setClaimingDedicated(false);
350
+ if (!response.ok) {
351
+ var _ref;
352
+ return void notifyRef.current((_ref = payload == null ? void 0 : payload.error) != null ? _ref : 'Could not ask for a sending domain for this site', {
353
+ variant: 'warning'
354
+ });
355
+ }
356
+ /*
357
+ * "Asked for", not "is being set up". The claim is the only thing this
358
+ * press completed; creating the domain happens on our side afterwards and
359
+ * can be held at the provider's domain allowance, so a message promising
360
+ * setup would be promising the half that is not ours to promise. The
361
+ * domain's row is where the answer arrives, which is why this points at
362
+ * it rather than restating it.
363
+ */ notifyRef.current(`${payload == null ? void 0 : payload.selected} has been asked for. Its row below says where the ` + 'setup has got to. Account email keeps sending meanwhile, and ' + 'campaigns can go out once the domain verifies.', {
364
+ variant: 'success'
365
+ });
366
+ await load();
367
+ }, [
368
+ call,
369
+ hostId,
370
+ claimingDedicated,
371
+ load
372
+ ]);
373
+ /**
374
+ * WHAT MAY BE DONE TO ONE DOMAIN, AND WHY NOT WHEN NOT.
375
+ *
376
+ * Every item here is an operation the routes behind this card can actually
377
+ * perform, and one whose gate this card can see. Nothing is offered on a
378
+ * guess: a menu entry that 403s teaches a reader the console does not know
379
+ * its own rules, and it costs them a round trip to find out.
380
+ *
381
+ * DISABLED WITH A REASON rather than hidden, for the reason
382
+ * `RowActionsMenuItem.disabledReason` exists: an absent control and an
383
+ * inapplicable one look identical, so a viewer who cannot remove a domain
384
+ * would otherwise be left wondering whether the console can remove domains
385
+ * at all.
386
+ *
387
+ * The gates are not one gate, because the routes are not one route:
388
+ *
389
+ * - Open is navigation and needs nothing.
390
+ * - Check DNS and Remove go to the DOMAINS route, which is owner/admin on
391
+ * the org AND carries the own-domain entitlement across the whole
392
+ * handler — so both conditions are read here.
393
+ * - Sending as a domain goes to the IDENTITY route, whose write gate is
394
+ * `org.settings` alone. It also refuses an unverified domain, which is
395
+ * a state this list can already see, so it is named rather than
396
+ * discovered.
397
+ */ const domainActions = useCallback((record)=>{
398
+ const working = Boolean(domainBusy);
399
+ const manage = (view == null ? void 0 : view.canManage) === true;
400
+ const entitled = (view == null ? void 0 : view.entitled) === true;
401
+ const selected = (view == null ? void 0 : view.selected) === record.domain;
402
+ const orgAdminReason = 'Managing sending domains needs the organization admin role.';
403
+ const domainRouteReason = !manage ? orgAdminReason : !entitled ? 'Managing sending domains needs a plan that carries sending as ' + 'your own domain.' : '';
404
+ return [
405
+ {
406
+ key: 'open',
407
+ label: 'Open domain',
408
+ icon: /*#__PURE__*/ _jsx(MdiIcon, {
409
+ path: mdiDnsOutline.path,
410
+ size: 0.8
411
+ }),
412
+ // An `href`, so the domain's page is middle-clickable and copyable
413
+ // like any other link rather than only reachable by this handler.
414
+ href: domainPath(record.domain)
415
+ },
416
+ {
417
+ key: 'verify',
418
+ label: 'Check DNS',
419
+ icon: /*#__PURE__*/ _jsx(MdiIcon, {
420
+ path: mdiRefresh.path,
421
+ size: 0.8
422
+ }),
423
+ disabled: working || record.status === 'requested' || Boolean(domainRouteReason),
424
+ disabledReason: record.status === 'requested' ? 'No records have been issued for this domain yet, so there ' + 'is nothing to look for. Open it to see what it is waiting on.' : domainRouteReason || undefined,
425
+ onClick: ()=>void handleVerifyDomain(record.domain)
426
+ },
427
+ {
428
+ key: 'use',
429
+ label: 'Send this site’s email as this domain',
430
+ icon: /*#__PURE__*/ _jsx(MdiIcon, {
431
+ path: mdiEmailCheckOutline.path,
432
+ size: 0.8
433
+ }),
434
+ disabled: working || selected || record.status !== 'verified' || !manage,
435
+ disabledReason: selected ? 'This site already sends as this domain.' : record.status !== 'verified' ? 'Only a verified domain can be sent as. Publish its records ' + 'and check DNS first.' : !manage ? 'Choosing what this site sends as needs the organization ' + 'admin role.' : undefined,
436
+ onClick: ()=>void handleUseDomain(record.domain)
437
+ },
438
+ {
439
+ key: 'release',
440
+ label: 'Remove domain',
441
+ icon: /*#__PURE__*/ _jsx(MdiIcon, {
442
+ path: mdiDeleteOutline.path,
443
+ size: 0.8
444
+ }),
445
+ destructive: true,
446
+ disabled: working || Boolean(domainRouteReason),
447
+ disabledReason: domainRouteReason || undefined,
448
+ onClick: ()=>void handleReleaseDomain(record.domain)
449
+ }
450
+ ];
451
+ }, [
452
+ domainBusy,
453
+ domainPath,
454
+ view == null ? void 0 : view.canManage,
455
+ view == null ? void 0 : view.entitled,
456
+ view == null ? void 0 : view.selected,
457
+ handleVerifyDomain,
458
+ handleUseDomain,
459
+ handleReleaseDomain
460
+ ]);
461
+ const domains = (_ref = view == null ? void 0 : view.domains) != null ? _ref : [];
462
+ const senders = (_ref1 = view == null ? void 0 : view.senders) != null ? _ref1 : [];
463
+ /*
464
+ * Whether this site has a domain of its own to name a mailbox on — the same
465
+ * question the route asks before it accepts one. A site on the shared pool
466
+ * has exactly one sender and its mailbox is fixed, so offering to add a
467
+ * second would offer a choice the write refuses.
468
+ */ const canAddSender = Boolean(view == null ? void 0 : view.selected);
469
+ return /*#__PURE__*/ _jsxs(CardDisplay, {
470
+ header: "Sending domains",
471
+ help: pluginDocsHelp('emailCampaigns', {
472
+ anchor: '#sending-domains'
473
+ }),
474
+ contentGutterX: true,
475
+ contentGutterY: true,
476
+ contentBordered: "all",
477
+ HeaderProps: {
478
+ action: (view == null ? void 0 : view.canManage) ? /*#__PURE__*/ _jsxs(Stack, {
479
+ direction: "row",
480
+ spacing: 1,
481
+ children: [
482
+ /*#__PURE__*/ _jsx(Button, {
483
+ size: "small",
484
+ variant: "outlined",
485
+ disabled: !view || !canAddSender,
486
+ onClick: ()=>setSenderEditor({
487
+ senderId: null
488
+ }),
489
+ children: 'Add sender'
490
+ }),
491
+ /*#__PURE__*/ _jsx(Button, {
492
+ size: "small",
493
+ variant: "contained",
494
+ disabled: !(view == null ? void 0 : view.entitled) || !(view == null ? void 0 : view.orgId),
495
+ onClick: ()=>setAdding(true),
496
+ children: 'Add domain'
497
+ })
498
+ ]
499
+ }) : null
500
+ },
501
+ children: [
502
+ /*#__PURE__*/ _jsxs(Stack, {
503
+ spacing: 1.5,
504
+ children: [
505
+ /*#__PURE__*/ _jsx(Typography, {
506
+ variant: "body2",
507
+ color: "text.secondary",
508
+ children: 'Receipts, password resets, booking confirmations and other ' + 'account email send on every plan, including Free. Nothing here ' + 'has to be finished first.'
509
+ }),
510
+ /*#__PURE__*/ _jsx(Typography, {
511
+ variant: "body2",
512
+ color: "text.secondary",
513
+ children: 'Campaigns send on the shared address too. Its delivery ' + 'reputation is pooled with the other sites on it, so campaigns ' + 'there are held to tighter complaint and bounce limits — a ' + 'domain of this site’s own lifts that, and is the name your ' + 'recipients will recognize.'
514
+ }),
515
+ view ? /*#__PURE__*/ _jsxs(Alert, {
516
+ severity: view.refusal ? 'warning' : 'info',
517
+ children: [
518
+ /*#__PURE__*/ _jsx(Typography, {
519
+ variant: "body2",
520
+ sx: {
521
+ fontWeight: 'bold'
522
+ },
523
+ children: view.refusal ? 'This site cannot send' : 'This site sends as'
524
+ }),
525
+ /*#__PURE__*/ _jsx(Typography, {
526
+ variant: "body2",
527
+ children: (_ref2 = (_view_refusal = view.refusal) == null ? void 0 : _view_refusal.message) != null ? _ref2 : view.identity
528
+ }),
529
+ view.identitySource === 'shared' && !view.refusal ? /*#__PURE__*/ _jsx(Typography, {
530
+ variant: "body2",
531
+ sx: {
532
+ mt: 0.5
533
+ },
534
+ children: 'Recipients see an Aglyn address, and replies still reach ' + 'you. The sending reputation on it is shared with the other ' + 'sites using it rather than being yours.'
535
+ }) : null,
536
+ view.fromName || view.replyTo ? /*#__PURE__*/ _jsxs(Typography, {
537
+ variant: "body2",
538
+ sx: {
539
+ mt: 0.5
540
+ },
541
+ children: [
542
+ view.fromName ? `Recipients see it from ${view.fromName}.` : 'Recipients see the address with no name in front of it.',
543
+ view.replyTo ? ` Replies go to ${view.replyTo}.` : ''
544
+ ]
545
+ }) : null,
546
+ view.localPart && !view.localPartInUse && !view.refusal ? /*#__PURE__*/ _jsx(Typography, {
547
+ variant: "body2",
548
+ sx: {
549
+ mt: 0.5
550
+ },
551
+ children: `This site is set to send as ${view.localPart}@, which ` + 'takes effect once it has a domain of its own. The shared ' + 'address has one fixed mailbox for every site on it.'
552
+ }) : null
553
+ ]
554
+ }) : null,
555
+ view ? /*#__PURE__*/ _jsxs(_Fragment, {
556
+ children: [
557
+ /*#__PURE__*/ _jsx(Divider, {}),
558
+ /*#__PURE__*/ _jsxs(Stack, {
559
+ direction: "row",
560
+ spacing: 0.5,
561
+ sx: {
562
+ alignItems: 'center'
563
+ },
564
+ children: [
565
+ /*#__PURE__*/ _jsx(Typography, {
566
+ variant: "body2",
567
+ sx: {
568
+ fontWeight: 'bold'
569
+ },
570
+ children: 'Senders'
571
+ }),
572
+ /*#__PURE__*/ _jsx(HelpTip, _extends({}, pluginDocsHelp('emailCampaigns', {
573
+ anchor: '#senders',
574
+ title: 'Senders',
575
+ excerpt: 'The addresses this site may send as — a mailbox, a name ' + 'and a reply address each. A campaign picks one; the ' + 'default is what an email that names no other goes out as.'
576
+ })))
577
+ ]
578
+ }),
579
+ /*#__PURE__*/ _jsx(Typography, {
580
+ variant: "body2",
581
+ color: "text.secondary",
582
+ children: 'The addresses this site may send as. Every email you compose ' + 'picks one of them; the default is what an email that names ' + 'no other goes out as.'
583
+ }),
584
+ !canAddSender ? /*#__PURE__*/ _jsx(Typography, {
585
+ variant: "body2",
586
+ color: "text.secondary",
587
+ children: 'This site sends on a shared Aglyn address, whose mailbox is ' + 'fixed and shared with the other sites on it, so it has one ' + 'sender. A domain of this site’s own is what makes a second ' + 'address possible.'
588
+ }) : null,
589
+ /*#__PURE__*/ _jsxs(ScrollTable, {
590
+ size: "small",
591
+ children: [
592
+ /*#__PURE__*/ _jsx(TableHead, {
593
+ children: /*#__PURE__*/ _jsxs(TableRow, {
594
+ children: [
595
+ /*#__PURE__*/ _jsx(TableCell, {
596
+ children: 'Sends as'
597
+ }),
598
+ /*#__PURE__*/ _jsx(TableCell, {
599
+ children: 'Name'
600
+ }),
601
+ /*#__PURE__*/ _jsx(TableCell, {
602
+ children: 'Replies to'
603
+ }),
604
+ /*#__PURE__*/ _jsx(TableCell, {
605
+ align: "right",
606
+ children: 'Default'
607
+ })
608
+ ]
609
+ })
610
+ }),
611
+ /*#__PURE__*/ _jsx(TableBody, {
612
+ children: senders.map((sender)=>{
613
+ var _sender_from, _sender_fromName, _sender_replyTo;
614
+ return /*#__PURE__*/ _jsxs(TableRow, {
615
+ hover: view.canManage,
616
+ sx: {
617
+ cursor: view.canManage ? 'pointer' : 'default'
618
+ },
619
+ onClick: view.canManage ? ()=>setSenderEditor({
620
+ senderId: sender.id
621
+ }) : undefined,
622
+ children: [
623
+ /*#__PURE__*/ _jsx(TableCell, {
624
+ sx: {
625
+ fontFamily: 'monospace'
626
+ },
627
+ children: (_sender_from = sender.from) != null ? _sender_from : `${sender.localPart}@`
628
+ }),
629
+ /*#__PURE__*/ _jsx(TableCell, {
630
+ children: (_sender_fromName = sender.fromName) != null ? _sender_fromName : '—'
631
+ }),
632
+ /*#__PURE__*/ _jsx(TableCell, {
633
+ children: (_sender_replyTo = sender.replyTo) != null ? _sender_replyTo : '—'
634
+ }),
635
+ /*#__PURE__*/ _jsx(TableCell, {
636
+ align: "right",
637
+ children: sender.isDefault ? /*#__PURE__*/ _jsx(Chip, {
638
+ size: "small",
639
+ label: "Default",
640
+ color: "primary"
641
+ }) : view.canManage ? /*#__PURE__*/ _jsxs(Stack, {
642
+ direction: "row",
643
+ spacing: 1,
644
+ sx: {
645
+ justifyContent: 'flex-end'
646
+ },
647
+ children: [
648
+ /*#__PURE__*/ _jsx(Button, {
649
+ size: "small",
650
+ disabled: senderBusy === sender.id,
651
+ onClick: (event)=>{
652
+ event.stopPropagation();
653
+ void handleSenderAction('makeDefaultSender', sender.id);
654
+ },
655
+ children: 'Make default'
656
+ }),
657
+ /*#__PURE__*/ _jsx(Button, {
658
+ size: "small",
659
+ color: "error",
660
+ disabled: senderBusy === sender.id,
661
+ onClick: (event)=>{
662
+ event.stopPropagation();
663
+ void handleSenderAction('deleteSender', sender.id);
664
+ },
665
+ children: 'Remove'
666
+ })
667
+ ]
668
+ }) : /*#__PURE__*/ _jsx(Typography, {
669
+ variant: "body2",
670
+ color: "text.secondary",
671
+ children: '—'
672
+ })
673
+ })
674
+ ]
675
+ }, sender.id);
676
+ })
677
+ })
678
+ ]
679
+ })
680
+ ]
681
+ }) : null,
682
+ view ? /*#__PURE__*/ _jsxs(_Fragment, {
683
+ children: [
684
+ /*#__PURE__*/ _jsx(Divider, {}),
685
+ /*#__PURE__*/ _jsxs(Stack, {
686
+ direction: "row",
687
+ spacing: 0.5,
688
+ sx: {
689
+ alignItems: 'center'
690
+ },
691
+ children: [
692
+ /*#__PURE__*/ _jsx(Typography, {
693
+ variant: "body2",
694
+ sx: {
695
+ fontWeight: 'bold'
696
+ },
697
+ children: 'Where this site’s mail can leave from'
698
+ }),
699
+ /*#__PURE__*/ _jsx(HelpTip, _extends({}, pluginDocsHelp('emailCampaigns', {
700
+ anchor: '#a-domain-we-set-up-is-a-request',
701
+ title: 'A domain we set up is asked for',
702
+ excerpt: 'The shared address is always there. A domain you own you ' + 'add yourself. A domain we set up is a request against a ' + 'limited allowance, so it can wait — receipts and account ' + 'email keep sending whichever you are on.'
703
+ })))
704
+ ]
705
+ }),
706
+ /*#__PURE__*/ _jsxs(Typography, {
707
+ variant: "body2",
708
+ color: "text.secondary",
709
+ children: [
710
+ /*#__PURE__*/ _jsx(Typography, {
711
+ component: "span",
712
+ variant: "body2",
713
+ sx: {
714
+ fontWeight: 'bold'
715
+ },
716
+ children: 'The shared Aglyn address. '
717
+ }),
718
+ 'Every plan, nothing to set up, and the one thing here you are ' + 'never waiting for. It carries receipts and account email ' + 'only, and its sending reputation is shared with the other ' + 'sites on it rather than being this site’s.'
719
+ ]
720
+ }),
721
+ /*#__PURE__*/ _jsxs(Typography, {
722
+ variant: "body2",
723
+ color: "text.secondary",
724
+ children: [
725
+ /*#__PURE__*/ _jsx(Typography, {
726
+ component: "span",
727
+ variant: "body2",
728
+ sx: {
729
+ fontWeight: 'bold'
730
+ },
731
+ children: 'A domain you already own. '
732
+ }),
733
+ (view.customDomainPlan ? `Sending as your own domain starts on the ${view.customDomainPlan} plan. ` : '') + 'Your own name in the From: line and a sending reputation ' + 'that is entirely yours. The trade is the DNS work: you ' + 'publish three records in your zone, and we never write to ' + 'it. Nothing on our side can hold this one up.'
734
+ ]
735
+ }),
736
+ /*#__PURE__*/ _jsxs(Typography, {
737
+ variant: "body2",
738
+ color: "text.secondary",
739
+ children: [
740
+ /*#__PURE__*/ _jsx(Typography, {
741
+ component: "span",
742
+ variant: "body2",
743
+ sx: {
744
+ fontWeight: 'bold'
745
+ },
746
+ children: 'A domain we set up. '
747
+ }),
748
+ view.platformDomain ? `This site has one — ${view.platformDomain} — with no ` + 'records for you to publish, and the sending reputation on ' + 'it is this site’s alone. Recipients see an address on our ' + 'domain rather than on yours.' : 'Nothing for you to publish, and a sending reputation this ' + 'site holds alone; recipients see an address on our domain ' + 'rather than on yours. ' + /*
749
+ * THE PART A MERCHANT WOULD OTHERWISE FEEL CHEATED BY, said
750
+ * before they press rather than in the refusal afterwards.
751
+ *
752
+ * Each of these domains is a slot in the mail provider's
753
+ * account-wide allowance plus three records in our own zone.
754
+ * A card that named a tier and stopped would be promising an
755
+ * inclusion the platform does not sell — and the sentence
756
+ * that has to travel with the correction is the one about
757
+ * what does not stop meanwhile, because "we might not have
758
+ * room" reads as an outage without it.
759
+ */ (view.dedicatedDomainPlan ? `It needs ${view.dedicatedDomainPlan}, and the plan ` + 'admits the request rather than handing you the domain: ' : 'It is a request rather than something a plan hands ' + 'you: ') + 'we hold a limited number of sending domains with our mail ' + 'provider, so one asked for when there is no room waits ' + 'until there is. Nothing stops while it waits — this site ' + 'keeps sending receipts and account email on the address ' + 'above, and only campaigns wait with the domain.'
760
+ ]
761
+ }),
762
+ ((_view_dedicated = view.dedicated) == null ? void 0 : _view_dedicated.available) && view.canManage ? /*#__PURE__*/ _jsx(Button, {
763
+ size: "small",
764
+ variant: "outlined",
765
+ sx: {
766
+ alignSelf: 'flex-start'
767
+ },
768
+ disabled: claimingDedicated,
769
+ onClick: ()=>void handleClaimDedicated(),
770
+ children: claimingDedicated ? 'Asking…' : view.dedicated.proposed ? `Ask for ${view.dedicated.proposed}` : 'Ask for a domain for this site'
771
+ }) : null,
772
+ !view.entitled ? /*#__PURE__*/ _jsx(Typography, {
773
+ variant: "body2",
774
+ color: "text.secondary",
775
+ children: 'Account email from this site sends without either of those ' + '— the address is shown above.'
776
+ }) : null,
777
+ /*#__PURE__*/ _jsx(Divider, {})
778
+ ]
779
+ }) : null,
780
+ view && !view.canManage ? /*#__PURE__*/ _jsx(Alert, {
781
+ severity: "info",
782
+ children: 'Adding a domain and choosing what this site sends as needs the ' + 'organization admin role. You can see the current state here.'
783
+ }) : null,
784
+ loading ? /*#__PURE__*/ _jsx(Typography, {
785
+ variant: "body2",
786
+ color: "text.secondary",
787
+ children: 'Loading…'
788
+ }) : domains.length ? /*#__PURE__*/ _jsxs(ScrollTable, {
789
+ size: "small",
790
+ children: [
791
+ /*#__PURE__*/ _jsx(TableHead, {
792
+ children: /*#__PURE__*/ _jsxs(TableRow, {
793
+ children: [
794
+ /*#__PURE__*/ _jsx(TableCell, {
795
+ children: 'Domain'
796
+ }),
797
+ /*#__PURE__*/ _jsx(TableCell, {
798
+ children: 'State'
799
+ }),
800
+ /*#__PURE__*/ _jsx(TableCell, {
801
+ children: 'Used by this site'
802
+ }),
803
+ /*#__PURE__*/ _jsx(TableCell, {
804
+ align: "right",
805
+ children: /*#__PURE__*/ _jsx(SrOnly, {
806
+ children: 'Actions'
807
+ })
808
+ })
809
+ ]
810
+ })
811
+ }),
812
+ /*#__PURE__*/ _jsx(TableBody, {
813
+ children: domains.map((record)=>{
814
+ var _record_lastIssueError;
815
+ const state = describeSendingDomain({
816
+ status: record.status,
817
+ // The list read does not carry the provider detail — the
818
+ // domain's own page does — so `requested` reads here as the
819
+ // waiting state it usually is, and the page is where the
820
+ // difference between "no credential" and "the provider
821
+ // refused" is spelled out.
822
+ pendingProvider: record.status === 'requested',
823
+ // The REASON is the exception to that, because one of its
824
+ // values is not a fault. A claim held at the provider's
825
+ // domain allowance would otherwise read here as "waiting on
826
+ // a signing key", whose whole instruction is to press a
827
+ // button that cannot move it.
828
+ issueError: (_record_lastIssueError = record.lastIssueError) != null ? _record_lastIssueError : null
829
+ });
830
+ return /*#__PURE__*/ _jsxs(TableRow, {
831
+ hover: true,
832
+ sx: {
833
+ cursor: 'pointer'
834
+ },
835
+ onClick: ()=>router.push(domainPath(record.domain)),
836
+ children: [
837
+ /*#__PURE__*/ _jsx(TableCell, {
838
+ sx: {
839
+ fontFamily: 'monospace'
840
+ },
841
+ children: record.domain
842
+ }),
843
+ /*#__PURE__*/ _jsx(TableCell, {
844
+ children: /*#__PURE__*/ _jsx(Chip, {
845
+ size: "small",
846
+ label: state.label,
847
+ color: state.color,
848
+ variant: state.sending ? 'filled' : 'outlined'
849
+ })
850
+ }),
851
+ /*#__PURE__*/ _jsx(TableCell, {
852
+ children: (view == null ? void 0 : view.selected) === record.domain ? /*#__PURE__*/ _jsx(Chip, {
853
+ size: "small",
854
+ label: "Sending as this",
855
+ color: "primary"
856
+ }) : /*#__PURE__*/ _jsx(Typography, {
857
+ variant: "body2",
858
+ color: "text.secondary",
859
+ children: '—'
860
+ })
861
+ }),
862
+ /*#__PURE__*/ _jsx(TableCell, {
863
+ align: "right",
864
+ padding: "none",
865
+ children: /*#__PURE__*/ _jsx(RowActionsMenu, {
866
+ label: record.domain,
867
+ items: domainActions(record)
868
+ })
869
+ })
870
+ ]
871
+ }, record.domain);
872
+ })
873
+ })
874
+ ]
875
+ }) : /*#__PURE__*/ _jsx(Typography, {
876
+ variant: "body2",
877
+ color: "text.secondary",
878
+ children: 'No domain has been added yet.'
879
+ })
880
+ ]
881
+ }),
882
+ /*#__PURE__*/ _jsx(SendingSenderDrawer, {
883
+ open: Boolean(senderEditor),
884
+ hostId: hostId,
885
+ view: view,
886
+ senderId: (_ref3 = senderEditor == null ? void 0 : senderEditor.senderId) != null ? _ref3 : null,
887
+ onClose: ()=>setSenderEditor(null),
888
+ onSaved: ()=>void load()
889
+ }),
890
+ /*#__PURE__*/ _jsx(NavigationDrawerComponent, {
891
+ open: adding,
892
+ anchor: "right",
893
+ variant: "temporary",
894
+ onClose: ()=>setAdding(false),
895
+ AppBarProps: {
896
+ color: 'surface'
897
+ },
898
+ appBarLeft: /*#__PURE__*/ _jsxs(_Fragment, {
899
+ children: [
900
+ /*#__PURE__*/ _jsxs(IconButton, {
901
+ color: "inherit",
902
+ edge: "start",
903
+ onClick: ()=>setAdding(false),
904
+ sx: {
905
+ mr: 2
906
+ },
907
+ children: [
908
+ /*#__PURE__*/ _jsx(MdiIcon, {
909
+ path: ICON_VARIANT_CLOSE.path
910
+ }),
911
+ /*#__PURE__*/ _jsx(SrOnly, {
912
+ children: "close drawer"
913
+ })
914
+ ]
915
+ }),
916
+ /*#__PURE__*/ _jsx(Typography, {
917
+ variant: "h6",
918
+ component: "div",
919
+ children: 'Add a sending domain'
920
+ })
921
+ ]
922
+ }),
923
+ children: /*#__PURE__*/ _jsx(Container, {
924
+ gutterY: true,
925
+ children: /*#__PURE__*/ _jsxs(Stack, {
926
+ spacing: 2,
927
+ children: [
928
+ /*#__PURE__*/ _jsx(Typography, {
929
+ variant: "body2",
930
+ color: "text.secondary",
931
+ children: 'The domain your mail should come from — the part after the @. ' + 'Use a domain you control the DNS for; you will be given ' + 'records to publish on it. Mailbox providers do not delegate ' + 'sending for their own names, so a Gmail or Outlook address ' + 'cannot be verified here.'
932
+ }),
933
+ /*#__PURE__*/ _jsx(TextField, {
934
+ label: "Domain",
935
+ value: newDomain,
936
+ onChange: (event)=>setNewDomain(event.target.value),
937
+ size: "small",
938
+ autoFocus: true,
939
+ placeholder: "acme.com",
940
+ helperText: "For example acme.com — not an address"
941
+ }),
942
+ addError ? /*#__PURE__*/ _jsx(Alert, {
943
+ severity: "error",
944
+ children: addError
945
+ }) : null,
946
+ /*#__PURE__*/ _jsx(Button, {
947
+ variant: "contained",
948
+ disabled: !newDomain.trim() || addingBusy,
949
+ onClick: ()=>void handleAdd(),
950
+ children: addingBusy ? 'Adding…' : 'Add domain'
951
+ })
952
+ ]
953
+ })
954
+ })
955
+ })
956
+ ]
957
+ });
958
+ }
959
+ SendingDomainsCard.displayName = 'SendingDomainsCard';
960
+ export default SendingDomainsCard;
961
+
962
+ //# sourceMappingURL=sending-domains-card.js.map