@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.
- package/LICENSE +201 -0
- package/README.md +7 -0
- package/package.json +58 -0
- package/src/index.d.ts +35 -0
- package/src/index.js +35 -0
- package/src/index.js.map +1 -0
- package/src/lib/components/campaign-design-create-widget.d.ts +21 -0
- package/src/lib/components/campaign-design-create-widget.js +61 -0
- package/src/lib/components/campaign-design-create-widget.js.map +1 -0
- package/src/lib/components/campaign-sender-editor-widget.d.ts +18 -0
- package/src/lib/components/campaign-sender-editor-widget.js +39 -0
- package/src/lib/components/campaign-sender-editor-widget.js.map +1 -0
- package/src/lib/components/campaign-topic-options-widget.d.ts +19 -0
- package/src/lib/components/campaign-topic-options-widget.js +48 -0
- package/src/lib/components/campaign-topic-options-widget.js.map +1 -0
- package/src/lib/components/campaign-topic-select.d.ts +32 -0
- package/src/lib/components/campaign-topic-select.js +87 -0
- package/src/lib/components/campaign-topic-select.js.map +1 -0
- package/src/lib/components/dynamic-list-rule-fields.d.ts +173 -0
- package/src/lib/components/dynamic-list-rule-fields.js +1473 -0
- package/src/lib/components/dynamic-list-rule-fields.js.map +1 -0
- package/src/lib/components/email-blocks.d.ts +111 -0
- package/src/lib/components/email-blocks.js +875 -0
- package/src/lib/components/email-blocks.js.map +1 -0
- package/src/lib/components/email-design-preview.d.ts +62 -0
- package/src/lib/components/email-design-preview.js +174 -0
- package/src/lib/components/email-design-preview.js.map +1 -0
- package/src/lib/components/email-screens-card.d.ts +42 -0
- package/src/lib/components/email-screens-card.js +277 -0
- package/src/lib/components/email-screens-card.js.map +1 -0
- package/src/lib/components/email-template-detail.d.ts +48 -0
- package/src/lib/components/email-template-detail.js +681 -0
- package/src/lib/components/email-template-detail.js.map +1 -0
- package/src/lib/components/email-topic-detail.d.ts +32 -0
- package/src/lib/components/email-topic-detail.js +293 -0
- package/src/lib/components/email-topic-detail.js.map +1 -0
- package/src/lib/components/email-topics-card.d.ts +46 -0
- package/src/lib/components/email-topics-card.js +327 -0
- package/src/lib/components/email-topics-card.js.map +1 -0
- package/src/lib/components/email-zones.d.ts +28 -0
- package/src/lib/components/email-zones.js +20 -0
- package/src/lib/components/email-zones.js.map +1 -0
- package/src/lib/components/emails-console-page.d.ts +32 -0
- package/src/lib/components/emails-console-page.js +229 -0
- package/src/lib/components/emails-console-page.js.map +1 -0
- package/src/lib/components/emails-console-sections.d.ts +36 -0
- package/src/lib/components/emails-console-sections.js +108 -0
- package/src/lib/components/emails-console-sections.js.map +1 -0
- package/src/lib/components/list-detail-card.d.ts +47 -0
- package/src/lib/components/list-detail-card.js +273 -0
- package/src/lib/components/list-detail-card.js.map +1 -0
- package/src/lib/components/list-edit-card.d.ts +11 -0
- package/src/lib/components/list-edit-card.js +287 -0
- package/src/lib/components/list-edit-card.js.map +1 -0
- package/src/lib/components/list-import-drawer.d.ts +22 -0
- package/src/lib/components/list-import-drawer.js +662 -0
- package/src/lib/components/list-import-drawer.js.map +1 -0
- package/src/lib/components/list-members-panel.d.ts +94 -0
- package/src/lib/components/list-members-panel.js +686 -0
- package/src/lib/components/list-members-panel.js.map +1 -0
- package/src/lib/components/lists-card.d.ts +28 -0
- package/src/lib/components/lists-card.js +377 -0
- package/src/lib/components/lists-card.js.map +1 -0
- package/src/lib/components/sending-domain-detail.d.ts +26 -0
- package/src/lib/components/sending-domain-detail.js +496 -0
- package/src/lib/components/sending-domain-detail.js.map +1 -0
- package/src/lib/components/sending-domains-card.d.ts +33 -0
- package/src/lib/components/sending-domains-card.js +962 -0
- package/src/lib/components/sending-domains-card.js.map +1 -0
- package/src/lib/components/sending-sender-drawer.d.ts +94 -0
- package/src/lib/components/sending-sender-drawer.js +543 -0
- package/src/lib/components/sending-sender-drawer.js.map +1 -0
- package/src/lib/components/suppressions-card.d.ts +49 -0
- package/src/lib/components/suppressions-card.js +639 -0
- package/src/lib/components/suppressions-card.js.map +1 -0
- package/src/lib/components/use-org-email-topics.d.ts +79 -0
- package/src/lib/components/use-org-email-topics.js +111 -0
- package/src/lib/components/use-org-email-topics.js.map +1 -0
- package/src/lib/constants/bundle-common.d.ts +18 -0
- package/src/lib/constants/bundle-common.js +18 -0
- package/src/lib/constants/bundle-common.js.map +1 -0
- package/src/lib/hooks/use-org-company-options.d.ts +20 -0
- package/src/lib/hooks/use-org-company-options.js +138 -0
- package/src/lib/hooks/use-org-company-options.js.map +1 -0
- package/src/lib/hooks/use-org-contact-fields.d.ts +40 -0
- package/src/lib/hooks/use-org-contact-fields.js +91 -0
- package/src/lib/hooks/use-org-contact-fields.js.map +1 -0
- package/src/lib/hooks/use-org-contact-segments.d.ts +16 -0
- package/src/lib/hooks/use-org-contact-segments.js +55 -0
- package/src/lib/hooks/use-org-contact-segments.js.map +1 -0
- package/src/lib/hooks/use-org-crm-views.d.ts +8 -0
- package/src/lib/hooks/use-org-crm-views.js +74 -0
- package/src/lib/hooks/use-org-crm-views.js.map +1 -0
- package/src/lib/hooks/use-org-lists.d.ts +8 -0
- package/src/lib/hooks/use-org-lists.js +47 -0
- package/src/lib/hooks/use-org-lists.js.map +1 -0
- package/src/lib/model/email-design-document.d.ts +52 -0
- package/src/lib/model/email-design-document.js +62 -0
- package/src/lib/model/email-design-document.js.map +1 -0
- package/src/lib/model/index.d.ts +64 -0
- package/src/lib/model/index.js +71 -0
- package/src/lib/model/index.js.map +1 -0
- package/src/lib/model/sending-domain-status.d.ts +99 -0
- package/src/lib/model/sending-domain-status.js +196 -0
- package/src/lib/model/sending-domain-status.js.map +1 -0
- package/src/lib/model/template-provenance.d.ts +113 -0
- package/src/lib/model/template-provenance.js +107 -0
- package/src/lib/model/template-provenance.js.map +1 -0
- package/src/lib/model/template-report.d.ts +158 -0
- package/src/lib/model/template-report.js +249 -0
- package/src/lib/model/template-report.js.map +1 -0
- package/src/lib/plugin.d.ts +27 -0
- package/src/lib/plugin.js +163 -0
- package/src/lib/plugin.js.map +1 -0
- package/src/lib/server-console.d.ts +116 -0
- package/src/lib/server-console.js +422 -0
- package/src/lib/server-console.js.map +1 -0
- package/src/lib/server-email-drafts.d.ts +104 -0
- package/src/lib/server-email-drafts.js +381 -0
- package/src/lib/server-email-drafts.js.map +1 -0
- package/src/lib/server-list-gate.d.ts +183 -0
- package/src/lib/server-list-gate.js +365 -0
- package/src/lib/server-list-gate.js.map +1 -0
- package/src/lib/server-list-import.d.ts +199 -0
- package/src/lib/server-list-import.js +632 -0
- package/src/lib/server-list-import.js.map +1 -0
- package/src/lib/server-suppressions.d.ts +135 -0
- package/src/lib/server-suppressions.js +295 -0
- package/src/lib/server-suppressions.js.map +1 -0
- package/src/lib/server.d.ts +19 -0
- package/src/lib/server.js +834 -0
- package/src/lib/server.js.map +1 -0
- package/src/lib/site.d.ts +26 -0
- package/src/lib/site.js +81 -0
- package/src/lib/site.js.map +1 -0
- package/src/lib/unsubscribe-link.d.ts +311 -0
- package/src/lib/unsubscribe-link.js +398 -0
- package/src/lib/unsubscribe-link.js.map +1 -0
- package/src/lib/utils/create-email-screen.d.ts +59 -0
- package/src/lib/utils/create-email-screen.js +59 -0
- package/src/lib/utils/create-email-screen.js.map +1 -0
- package/src/lib/utils/generate-preset-id.d.ts +19 -0
- package/src/lib/utils/generate-preset-id.js +25 -0
- 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
|