@aglyn/plugins-outreach 1.0.0-beta.181 → 1.0.0-beta.182
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/package.json +14 -13
- package/src/lib/components/link-domains-card.d.ts +28 -0
- package/src/lib/components/link-domains-card.js +285 -0
- package/src/lib/components/link-domains-card.js.map +1 -0
- package/src/lib/components/mailboxes-section.js +5 -1
- package/src/lib/components/mailboxes-section.js.map +1 -1
- package/src/lib/components/use-outreach-api.d.ts +5 -1
- package/src/lib/components/use-outreach-api.js +10 -0
- package/src/lib/components/use-outreach-api.js.map +1 -1
- package/src/lib/constants/api-routes.d.ts +6 -0
- package/src/lib/constants/api-routes.js +5 -0
- package/src/lib/constants/api-routes.js.map +1 -1
- package/src/lib/engine/sequence-validation.js +4 -1
- package/src/lib/engine/sequence-validation.js.map +1 -1
- package/src/lib/model/outreach-api.d.ts +48 -1
- package/src/lib/model/outreach-api.js.map +1 -1
- package/src/lib/routes/link-domain-routes.d.ts +65 -0
- package/src/lib/routes/link-domain-routes.js +142 -0
- package/src/lib/routes/link-domain-routes.js.map +1 -0
- package/src/lib/routes/platform-deps.d.ts +2 -1
- package/src/lib/routes/platform-deps.js +2 -1
- package/src/lib/routes/platform-deps.js.map +1 -1
- package/src/lib/routes/register-routes.d.ts +3 -0
- package/src/lib/routes/register-routes.js +19 -0
- package/src/lib/routes/register-routes.js.map +1 -1
- package/src/lib/runtime/click-link.d.ts +10 -3
- package/src/lib/runtime/click-link.js +18 -8
- package/src/lib/runtime/click-link.js.map +1 -1
- package/src/lib/runtime/platform-runtime-deps.js +13 -3
- package/src/lib/runtime/platform-runtime-deps.js.map +1 -1
- package/src/lib/runtime/runtime-deps.d.ts +15 -3
- package/src/lib/runtime/runtime-deps.js.map +1 -1
- package/src/lib/runtime/send-job.js +22 -2
- package/src/lib/runtime/send-job.js.map +1 -1
- package/src/lib/runtime/unsubscribe-link.js +17 -9
- package/src/lib/runtime/unsubscribe-link.js.map +1 -1
- package/src/lib/transport/rfc5322.d.ts +6 -2
- package/src/lib/transport/rfc5322.js +6 -2
- package/src/lib/transport/rfc5322.js.map +1 -1
- package/src/lib/transport/send-message.js +11 -8
- package/src/lib/transport/send-message.js.map +1 -1
package/README.md
CHANGED
|
@@ -99,7 +99,8 @@ console alone — each run opens a rep's sealed grant:
|
|
|
99
99
|
| `runtime/send-job` | `outreach-send`: due enrollments claimed in a transaction that reserves the mailbox's day, gates asked again, the email composed — with the `List-Unsubscribe` header when the sequence turns it on (AGL-3296), and its links' `outreachLinks` documents written first when it tracks clicks (AGL-3297) — and sent, the step recorded and filed on the contact's timeline; task steps file the rep's task |
|
|
100
100
|
| `runtime/sync-job` | `outreach-sync`: replies, out-of-office answers, opt-outs and bounces read with `format=full` and classified by the engine; the mailbox's health evidence and its automatic pause |
|
|
101
101
|
| `runtime/unsubscribe-link`, `runtime/unsubscribe-route` | the signed one-click link (`GET` and RFC 8058 `POST /api/outreach/unsubscribe`), a recipient link that outlives a paused rollout, and the `+unsubscribe` mailto |
|
|
102
|
-
| `runtime/click-link`, `runtime/click-route` | click tracking: the short link every send carries (`GET /api/outreach/l/<id>`, resolved through the top-level `outreachLinks` document, AGL-3297) and the signed `GET /api/outreach/click?t=…` link older emails carry; both recipient links, redirect first, then the click counted once per person with scanners kept apart |
|
|
102
|
+
| `runtime/click-link`, `runtime/click-route` | click tracking: the short link every send carries (`GET /api/outreach/l/<id>`, resolved through the top-level `outreachLinks` document, AGL-3297) and the signed `GET /api/outreach/click?t=…` link older emails carry; both recipient links, redirect first, then the click counted once per person with scanners kept apart; on the mailbox domain's verified `links.` host as `https://links.<domain>/<id>` (AGL-3306), which the console middleware rewrites to the same route |
|
|
103
|
+
| `routes/link-domain-routes`, `components/link-domains-card` | Sequences → Mailboxes → **Link domains** (AGL-3306): `outreach/link-domains` lists each mailbox domain's `links.` host and sets one up, checks it or removes it through `@aglyn/tenant-data-admin/server/tracking-hosts` — the domain driver attaches it, an HTTPS probe verifies it; set-up and removal are an owner's or admin's |
|
|
103
104
|
| `runtime/enrollment-events` | status changes as the engine's events, and an opt-out recorded on every list |
|
|
104
105
|
| `runtime/mailbox-notices` | the owner told, once, when a mailbox pauses itself or needs reconnecting |
|
|
105
106
|
| `runtime/timeline` | the contact's timeline, through the core's record-timeline seam |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aglyn/plugins-outreach",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.182",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"homepage": "https://aglyn.com",
|
|
6
6
|
"repository": {
|
|
@@ -25,18 +25,19 @@
|
|
|
25
25
|
"./package.json": "./package.json"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@aglyn/aglyn": "1.0.0-beta.
|
|
29
|
-
"@aglyn/shared-data-mdi": "1.0.0-beta.
|
|
30
|
-
"@aglyn/shared-ui-email-campaigns": "1.0.0-beta.
|
|
31
|
-
"@aglyn/shared-ui-jsx": "1.0.0-beta.
|
|
32
|
-
"@aglyn/shared-ui-next": "1.0.0-beta.
|
|
33
|
-
"@aglyn/shared-ui-snackstack": "1.0.0-beta.
|
|
34
|
-
"@aglyn/shared-util-
|
|
35
|
-
"@aglyn/shared-util-
|
|
36
|
-
"@aglyn/shared-util-
|
|
37
|
-
"@aglyn/
|
|
38
|
-
"@aglyn/tenant-
|
|
39
|
-
"@aglyn/tenant-
|
|
28
|
+
"@aglyn/aglyn": "1.0.0-beta.182",
|
|
29
|
+
"@aglyn/shared-data-mdi": "1.0.0-beta.182",
|
|
30
|
+
"@aglyn/shared-ui-email-campaigns": "1.0.0-beta.182",
|
|
31
|
+
"@aglyn/shared-ui-jsx": "1.0.0-beta.182",
|
|
32
|
+
"@aglyn/shared-ui-next": "1.0.0-beta.182",
|
|
33
|
+
"@aglyn/shared-ui-snackstack": "1.0.0-beta.182",
|
|
34
|
+
"@aglyn/shared-util-email": "1.0.0-beta.182",
|
|
35
|
+
"@aglyn/shared-util-http": "1.0.0-beta.182",
|
|
36
|
+
"@aglyn/shared-util-timestamp": "1.0.0-beta.182",
|
|
37
|
+
"@aglyn/shared-util-tools": "1.0.0-beta.182",
|
|
38
|
+
"@aglyn/tenant-data-admin": "1.0.0-beta.182",
|
|
39
|
+
"@aglyn/tenant-feature-instance": "1.0.0-beta.182",
|
|
40
|
+
"@aglyn/tenant-runtime": "1.0.0-beta.182",
|
|
40
41
|
"@swc/helpers": "0.5.23"
|
|
41
42
|
},
|
|
42
43
|
"peerDependencies": {
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { OutreachLinkDomainStatus } from '../model/outreach-api';
|
|
2
|
+
export interface OutreachLinkDomainsCardProps {
|
|
3
|
+
/** The organization the hub is mounted under; `null` until the shell knows it. */
|
|
4
|
+
orgId: string | null;
|
|
5
|
+
}
|
|
6
|
+
/** Where a link domain stands, as the card's chip says it. */
|
|
7
|
+
export declare const OUTREACH_LINK_DOMAIN_STATUS_LABELS: Record<OutreachLinkDomainStatus, string>;
|
|
8
|
+
/** Accessible names of the card's controls, spelled once for the specs. */
|
|
9
|
+
export declare const LINK_DOMAIN_LABELS: {
|
|
10
|
+
readonly setUp: (host: string) => string;
|
|
11
|
+
readonly check: (host: string) => string;
|
|
12
|
+
readonly remove: (host: string) => string;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Sequences → Mailboxes → Link domains (AGL-3306): the host tracked links in
|
|
16
|
+
* a mailbox's email point at.
|
|
17
|
+
*
|
|
18
|
+
* With **Count link clicks** on, every link in a sequence email is a short
|
|
19
|
+
* link. It reads `https://links.<the mailbox's domain>/…` once that host is
|
|
20
|
+
* set up here and verified — the same `links.` name, CNAME and check a
|
|
21
|
+
* sending domain's campaign click tracking uses — and the app's own address
|
|
22
|
+
* until then. A link on your own domain looks like the rest of your email.
|
|
23
|
+
*/
|
|
24
|
+
export declare function OutreachLinkDomainsCard(props: OutreachLinkDomainsCardProps): import("react").JSX.Element;
|
|
25
|
+
export declare namespace OutreachLinkDomainsCard {
|
|
26
|
+
var displayName: string;
|
|
27
|
+
}
|
|
28
|
+
export default OutreachLinkDomainsCard;
|
|
@@ -0,0 +1,285 @@
|
|
|
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 { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
18
|
+
import { pluginDocsHelp } from "@aglyn/aglyn";
|
|
19
|
+
import { CardDisplay, useConfirmationContext } from "@aglyn/shared-ui-jsx";
|
|
20
|
+
import { useSnackbar } from "@aglyn/shared-ui-snackstack";
|
|
21
|
+
import { Alert, Box, Button, Chip, Divider, Stack, Typography } from "@mui/material";
|
|
22
|
+
import { useCallback, useEffect, useState } from "react";
|
|
23
|
+
import { OutreachLoading, OutreachLoadProblem } from "./outreach-ui.js";
|
|
24
|
+
import { OutreachRouteError, useOutreachApi } from "./use-outreach-api.js";
|
|
25
|
+
/** Where a link domain stands, as the card's chip says it. */ export const OUTREACH_LINK_DOMAIN_STATUS_LABELS = {
|
|
26
|
+
'not-set-up': 'Not set up',
|
|
27
|
+
requested: 'Not added yet',
|
|
28
|
+
'records-issued': 'Waiting for DNS',
|
|
29
|
+
verified: 'Verified',
|
|
30
|
+
failed: 'Not answering'
|
|
31
|
+
};
|
|
32
|
+
const STATUS_COLORS = {
|
|
33
|
+
'not-set-up': 'default',
|
|
34
|
+
requested: 'warning',
|
|
35
|
+
'records-issued': 'warning',
|
|
36
|
+
verified: 'success',
|
|
37
|
+
failed: 'error'
|
|
38
|
+
};
|
|
39
|
+
/** Accessible names of the card's controls, spelled once for the specs. */ export const LINK_DOMAIN_LABELS = {
|
|
40
|
+
setUp: (host)=>`Set up ${host}`,
|
|
41
|
+
check: (host)=>`Check ${host}`,
|
|
42
|
+
remove: (host)=>`Remove ${host}`
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Sequences → Mailboxes → Link domains (AGL-3306): the host tracked links in
|
|
46
|
+
* a mailbox's email point at.
|
|
47
|
+
*
|
|
48
|
+
* With **Count link clicks** on, every link in a sequence email is a short
|
|
49
|
+
* link. It reads `https://links.<the mailbox's domain>/…` once that host is
|
|
50
|
+
* set up here and verified — the same `links.` name, CNAME and check a
|
|
51
|
+
* sending domain's campaign click tracking uses — and the app's own address
|
|
52
|
+
* until then. A link on your own domain looks like the rest of your email.
|
|
53
|
+
*/ export function OutreachLinkDomainsCard(props) {
|
|
54
|
+
const { orgId } = props;
|
|
55
|
+
const api = useOutreachApi(orgId);
|
|
56
|
+
const { enqueueSnackbar } = useSnackbar();
|
|
57
|
+
const { confirm } = useConfirmationContext();
|
|
58
|
+
const [load, setLoad] = useState({
|
|
59
|
+
status: 'loading'
|
|
60
|
+
});
|
|
61
|
+
const [busy, setBusy] = useState(null);
|
|
62
|
+
useEffect(()=>{
|
|
63
|
+
if (!orgId) return undefined;
|
|
64
|
+
let current = true;
|
|
65
|
+
setLoad({
|
|
66
|
+
status: 'loading'
|
|
67
|
+
});
|
|
68
|
+
api.readLinkDomains().then((data)=>current && setLoad({
|
|
69
|
+
status: 'ready',
|
|
70
|
+
data
|
|
71
|
+
})).catch((error)=>current && setLoad({
|
|
72
|
+
status: error instanceof OutreachRouteError && error.refused ? 'refused' : 'error'
|
|
73
|
+
}));
|
|
74
|
+
return ()=>{
|
|
75
|
+
current = false;
|
|
76
|
+
};
|
|
77
|
+
}, [
|
|
78
|
+
api,
|
|
79
|
+
orgId
|
|
80
|
+
]);
|
|
81
|
+
const change = useCallback(async (action, row)=>{
|
|
82
|
+
if (action === 'remove') {
|
|
83
|
+
const confirmed = await confirm({
|
|
84
|
+
title: `Remove ${row.host}?`,
|
|
85
|
+
description: `Links already sent on ${row.host} stop working, and new sequence emails use this ` + 'app’s address instead. Remove it only once no one will click those links again.',
|
|
86
|
+
confirmationText: 'Remove',
|
|
87
|
+
confirmationButtonProps: {
|
|
88
|
+
color: 'error'
|
|
89
|
+
}
|
|
90
|
+
}).then(()=>true).catch(()=>false);
|
|
91
|
+
if (!confirmed) return;
|
|
92
|
+
}
|
|
93
|
+
setBusy(row.domain);
|
|
94
|
+
try {
|
|
95
|
+
var _ref;
|
|
96
|
+
const answer = await api.changeLinkDomain(action, row.domain);
|
|
97
|
+
setLoad({
|
|
98
|
+
status: 'ready',
|
|
99
|
+
data: answer
|
|
100
|
+
});
|
|
101
|
+
const now = answer.domains.find((entry)=>entry.domain === row.domain);
|
|
102
|
+
enqueueSnackbar(action === 'remove' ? `${row.host} is no longer a link domain.` : (now == null ? void 0 : now.status) === 'verified' ? `${row.host} is verified. Tracked links now use it.` : action === 'set-up' ? `${row.host} is set up. Publish the DNS record, then check it.` : (_ref = now == null ? void 0 : now.detail) != null ? _ref : `${row.host} is not verified yet.`, {
|
|
103
|
+
variant: (now == null ? void 0 : now.status) === 'verified' || action === 'remove' ? 'success' : 'info'
|
|
104
|
+
});
|
|
105
|
+
} catch (error) {
|
|
106
|
+
enqueueSnackbar(error.message, {
|
|
107
|
+
variant: 'error',
|
|
108
|
+
allowDuplicate: true
|
|
109
|
+
});
|
|
110
|
+
} finally{
|
|
111
|
+
setBusy(null);
|
|
112
|
+
}
|
|
113
|
+
}, [
|
|
114
|
+
api,
|
|
115
|
+
confirm,
|
|
116
|
+
enqueueSnackbar
|
|
117
|
+
]);
|
|
118
|
+
return /*#__PURE__*/ _jsx(CardDisplay, {
|
|
119
|
+
header: "Link domains",
|
|
120
|
+
help: pluginDocsHelp('sequences', {
|
|
121
|
+
anchor: '#link-domains'
|
|
122
|
+
}),
|
|
123
|
+
contentGutterX: true,
|
|
124
|
+
contentGutterY: true,
|
|
125
|
+
children: /*#__PURE__*/ _jsxs(Stack, {
|
|
126
|
+
spacing: 2,
|
|
127
|
+
children: [
|
|
128
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
129
|
+
variant: "body2",
|
|
130
|
+
color: "text.secondary",
|
|
131
|
+
children: 'With Count link clicks on, the links in a sequence email are short links. Set up your ' + 'mailbox domain’s links host and they read links.<your domain> instead of this app’s ' + 'address — your own domain, like the rest of the email.'
|
|
132
|
+
}),
|
|
133
|
+
load.status === 'loading' ? /*#__PURE__*/ _jsx(OutreachLoading, {
|
|
134
|
+
label: "Loading link domains…"
|
|
135
|
+
}) : null,
|
|
136
|
+
load.status === 'error' || load.status === 'refused' ? /*#__PURE__*/ _jsx(OutreachLoadProblem, {
|
|
137
|
+
status: load.status,
|
|
138
|
+
what: "the link domains"
|
|
139
|
+
}) : null,
|
|
140
|
+
load.status === 'ready' && !load.data.domains.length ? /*#__PURE__*/ _jsx(Typography, {
|
|
141
|
+
variant: "body2",
|
|
142
|
+
color: "text.secondary",
|
|
143
|
+
children: "Connect a mailbox on your own domain to set up its link domain."
|
|
144
|
+
}) : null,
|
|
145
|
+
load.status === 'ready' ? load.data.domains.map((row, index)=>/*#__PURE__*/ _jsxs(Stack, {
|
|
146
|
+
spacing: 1.5,
|
|
147
|
+
children: [
|
|
148
|
+
index > 0 ? /*#__PURE__*/ _jsx(Divider, {}) : null,
|
|
149
|
+
/*#__PURE__*/ _jsx(LinkDomainRow, {
|
|
150
|
+
row: row,
|
|
151
|
+
canManage: load.data.canManage,
|
|
152
|
+
busy: busy !== null,
|
|
153
|
+
onAction: (action)=>void change(action, row)
|
|
154
|
+
})
|
|
155
|
+
]
|
|
156
|
+
}, row.domain)) : null
|
|
157
|
+
]
|
|
158
|
+
})
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
OutreachLinkDomainsCard.displayName = 'OutreachLinkDomainsCard';
|
|
162
|
+
function LinkDomainRow(props) {
|
|
163
|
+
const { row, canManage, busy, onAction } = props;
|
|
164
|
+
const setUp = row.status !== 'not-set-up';
|
|
165
|
+
return /*#__PURE__*/ _jsxs(Stack, {
|
|
166
|
+
spacing: 1,
|
|
167
|
+
children: [
|
|
168
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
169
|
+
direction: "row",
|
|
170
|
+
spacing: 1,
|
|
171
|
+
sx: {
|
|
172
|
+
alignItems: 'center',
|
|
173
|
+
flexWrap: 'wrap'
|
|
174
|
+
},
|
|
175
|
+
children: [
|
|
176
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
177
|
+
variant: "body2",
|
|
178
|
+
sx: {
|
|
179
|
+
fontWeight: 500,
|
|
180
|
+
wordBreak: 'break-all'
|
|
181
|
+
},
|
|
182
|
+
children: row.host
|
|
183
|
+
}),
|
|
184
|
+
/*#__PURE__*/ _jsx(Chip, {
|
|
185
|
+
size: "small",
|
|
186
|
+
variant: "outlined",
|
|
187
|
+
color: STATUS_COLORS[row.status],
|
|
188
|
+
label: OUTREACH_LINK_DOMAIN_STATUS_LABELS[row.status]
|
|
189
|
+
})
|
|
190
|
+
]
|
|
191
|
+
}),
|
|
192
|
+
row.linkPrefix ? /*#__PURE__*/ _jsx(Typography, {
|
|
193
|
+
variant: "caption",
|
|
194
|
+
color: "text.secondary",
|
|
195
|
+
sx: {
|
|
196
|
+
wordBreak: 'break-all'
|
|
197
|
+
},
|
|
198
|
+
children: `Links in email from ${row.domain} read ${row.linkPrefix}…`
|
|
199
|
+
}) : null,
|
|
200
|
+
row.detail && row.status !== 'verified' ? /*#__PURE__*/ _jsx(Alert, {
|
|
201
|
+
severity: "info",
|
|
202
|
+
children: row.detail
|
|
203
|
+
}) : null,
|
|
204
|
+
setUp && row.status !== 'verified' && row.records.length ? /*#__PURE__*/ _jsx(Stack, {
|
|
205
|
+
spacing: 1,
|
|
206
|
+
component: "dl",
|
|
207
|
+
sx: {
|
|
208
|
+
m: 0
|
|
209
|
+
},
|
|
210
|
+
children: row.records.map((record)=>/*#__PURE__*/ _jsxs(Box, {
|
|
211
|
+
sx: {
|
|
212
|
+
border: 1,
|
|
213
|
+
borderColor: 'divider',
|
|
214
|
+
borderRadius: 1,
|
|
215
|
+
p: 1.5
|
|
216
|
+
},
|
|
217
|
+
children: [
|
|
218
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
219
|
+
variant: "caption",
|
|
220
|
+
component: "dt",
|
|
221
|
+
color: "text.secondary",
|
|
222
|
+
children: `${record.type} · ${record.name}${record.required ? '' : ' · if needed'}`
|
|
223
|
+
}),
|
|
224
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
225
|
+
variant: "body2",
|
|
226
|
+
component: "dd",
|
|
227
|
+
sx: {
|
|
228
|
+
m: 0,
|
|
229
|
+
fontFamily: 'monospace',
|
|
230
|
+
wordBreak: 'break-all'
|
|
231
|
+
},
|
|
232
|
+
children: record.value
|
|
233
|
+
}),
|
|
234
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
235
|
+
variant: "caption",
|
|
236
|
+
color: "text.secondary",
|
|
237
|
+
children: record.note
|
|
238
|
+
})
|
|
239
|
+
]
|
|
240
|
+
}, `${record.type}:${record.name}:${record.value}`))
|
|
241
|
+
}) : null,
|
|
242
|
+
setUp && row.status !== 'verified' && !row.records.length ? /*#__PURE__*/ _jsx(Typography, {
|
|
243
|
+
variant: "body2",
|
|
244
|
+
color: "text.secondary",
|
|
245
|
+
children: `Point ${row.host} at this app — a CNAME to its address, or your proxy’s — then check it.`
|
|
246
|
+
}) : null,
|
|
247
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
248
|
+
direction: "row",
|
|
249
|
+
spacing: 1,
|
|
250
|
+
sx: {
|
|
251
|
+
flexWrap: 'wrap'
|
|
252
|
+
},
|
|
253
|
+
children: [
|
|
254
|
+
!setUp && canManage ? /*#__PURE__*/ _jsx(Button, {
|
|
255
|
+
variant: "outlined",
|
|
256
|
+
size: "small",
|
|
257
|
+
disabled: busy,
|
|
258
|
+
"aria-label": LINK_DOMAIN_LABELS.setUp(row.host),
|
|
259
|
+
onClick: ()=>onAction('set-up'),
|
|
260
|
+
children: "Set up"
|
|
261
|
+
}) : null,
|
|
262
|
+
setUp ? /*#__PURE__*/ _jsx(Button, {
|
|
263
|
+
variant: "outlined",
|
|
264
|
+
size: "small",
|
|
265
|
+
disabled: busy,
|
|
266
|
+
"aria-label": LINK_DOMAIN_LABELS.check(row.host),
|
|
267
|
+
onClick: ()=>onAction('check'),
|
|
268
|
+
children: "Check"
|
|
269
|
+
}) : null,
|
|
270
|
+
setUp && canManage ? /*#__PURE__*/ _jsx(Button, {
|
|
271
|
+
color: "error",
|
|
272
|
+
size: "small",
|
|
273
|
+
disabled: busy,
|
|
274
|
+
"aria-label": LINK_DOMAIN_LABELS.remove(row.host),
|
|
275
|
+
onClick: ()=>onAction('remove'),
|
|
276
|
+
children: "Remove"
|
|
277
|
+
}) : null
|
|
278
|
+
]
|
|
279
|
+
})
|
|
280
|
+
]
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
export default OutreachLinkDomainsCard;
|
|
284
|
+
|
|
285
|
+
//# sourceMappingURL=link-domains-card.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/link-domains-card.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { pluginDocsHelp } from '@aglyn/aglyn'\nimport { CardDisplay, useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { Alert, Box, Button, Chip, Divider, Stack, Typography } from '@mui/material'\nimport { useCallback, useEffect, useState } from 'react'\nimport type {\n OutreachLinkDomain,\n OutreachLinkDomainRequest,\n OutreachLinkDomainStatus,\n OutreachLinkDomainsResponse,\n} from '../model/outreach-api'\nimport { OutreachLoading, OutreachLoadProblem } from './outreach-ui'\nimport { OutreachRouteError, useOutreachApi } from './use-outreach-api'\n\nexport interface OutreachLinkDomainsCardProps {\n /** The organization the hub is mounted under; `null` until the shell knows it. */\n orgId: string | null\n}\n\n/** Where a link domain stands, as the card's chip says it. */\nexport const OUTREACH_LINK_DOMAIN_STATUS_LABELS: Record<OutreachLinkDomainStatus, string> = {\n 'not-set-up': 'Not set up',\n requested: 'Not added yet',\n 'records-issued': 'Waiting for DNS',\n verified: 'Verified',\n failed: 'Not answering',\n}\n\nconst STATUS_COLORS: Record<OutreachLinkDomainStatus, 'default' | 'success' | 'warning' | 'error'> = {\n 'not-set-up': 'default',\n requested: 'warning',\n 'records-issued': 'warning',\n verified: 'success',\n failed: 'error',\n}\n\n/** Accessible names of the card's controls, spelled once for the specs. */\nexport const LINK_DOMAIN_LABELS = {\n setUp: (host: string) => `Set up ${host}`,\n check: (host: string) => `Check ${host}`,\n remove: (host: string) => `Remove ${host}`,\n} as const\n\ntype Load =\n | { status: 'loading' }\n | { status: 'ready'; data: OutreachLinkDomainsResponse }\n | { status: 'error' | 'refused' }\n\n/**\n * Sequences → Mailboxes → Link domains (AGL-3306): the host tracked links in\n * a mailbox's email point at.\n *\n * With **Count link clicks** on, every link in a sequence email is a short\n * link. It reads `https://links.<the mailbox's domain>/…` once that host is\n * set up here and verified — the same `links.` name, CNAME and check a\n * sending domain's campaign click tracking uses — and the app's own address\n * until then. A link on your own domain looks like the rest of your email.\n */\nexport function OutreachLinkDomainsCard(props: OutreachLinkDomainsCardProps) {\n const { orgId } = props\n const api = useOutreachApi(orgId)\n const { enqueueSnackbar } = useSnackbar()\n const { confirm } = useConfirmationContext()\n const [load, setLoad] = useState<Load>({ status: 'loading' })\n const [busy, setBusy] = useState<string | null>(null)\n\n useEffect(() => {\n if (!orgId) return undefined\n let current = true\n setLoad({ status: 'loading' })\n api\n .readLinkDomains()\n .then((data) => current && setLoad({ status: 'ready', data }))\n .catch(\n (error: Error) =>\n current &&\n setLoad({ status: error instanceof OutreachRouteError && error.refused ? 'refused' : 'error' }),\n )\n return () => {\n current = false\n }\n }, [api, orgId])\n\n const change = useCallback(\n async (action: OutreachLinkDomainRequest['action'], row: OutreachLinkDomain) => {\n if (action === 'remove') {\n const confirmed = await confirm({\n title: `Remove ${row.host}?`,\n description:\n `Links already sent on ${row.host} stop working, and new sequence emails use this ` +\n 'app’s address instead. Remove it only once no one will click those links again.',\n confirmationText: 'Remove',\n confirmationButtonProps: { color: 'error' },\n })\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n }\n setBusy(row.domain)\n try {\n const answer = await api.changeLinkDomain(action, row.domain)\n setLoad({ status: 'ready', data: answer })\n const now = answer.domains.find((entry) => entry.domain === row.domain)\n enqueueSnackbar(\n action === 'remove'\n ? `${row.host} is no longer a link domain.`\n : now?.status === 'verified'\n ? `${row.host} is verified. Tracked links now use it.`\n : action === 'set-up'\n ? `${row.host} is set up. Publish the DNS record, then check it.`\n : (now?.detail ?? `${row.host} is not verified yet.`),\n { variant: now?.status === 'verified' || action === 'remove' ? 'success' : 'info' },\n )\n } catch (error) {\n enqueueSnackbar((error as Error).message, { variant: 'error', allowDuplicate: true })\n } finally {\n setBusy(null)\n }\n },\n [api, confirm, enqueueSnackbar],\n )\n\n return (\n <CardDisplay\n header=\"Link domains\"\n help={pluginDocsHelp('sequences', { anchor: '#link-domains' })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={2}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'With Count link clicks on, the links in a sequence email are short links. Set up your ' +\n 'mailbox domain’s links host and they read links.<your domain> instead of this app’s ' +\n 'address — your own domain, like the rest of the email.'}\n </Typography>\n {load.status === 'loading' ? <OutreachLoading label=\"Loading link domains…\" /> : null}\n {load.status === 'error' || load.status === 'refused' ? (\n <OutreachLoadProblem status={load.status} what=\"the link domains\" />\n ) : null}\n {load.status === 'ready' && !load.data.domains.length ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n Connect a mailbox on your own domain to set up its link domain.\n </Typography>\n ) : null}\n {load.status === 'ready'\n ? load.data.domains.map((row, index) => (\n <Stack key={row.domain} spacing={1.5}>\n {index > 0 ? <Divider /> : null}\n <LinkDomainRow\n row={row}\n canManage={load.data.canManage}\n busy={busy !== null}\n onAction={(action) => void change(action, row)}\n />\n </Stack>\n ))\n : null}\n </Stack>\n </CardDisplay>\n )\n}\nOutreachLinkDomainsCard.displayName = 'OutreachLinkDomainsCard'\n\nfunction LinkDomainRow(props: {\n row: OutreachLinkDomain\n canManage: boolean\n busy: boolean\n onAction: (action: OutreachLinkDomainRequest['action']) => void\n}) {\n const { row, canManage, busy, onAction } = props\n const setUp = row.status !== 'not-set-up'\n return (\n <Stack spacing={1}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap' }}>\n <Typography variant=\"body2\" sx={{ fontWeight: 500, wordBreak: 'break-all' }}>\n {row.host}\n </Typography>\n <Chip\n size=\"small\"\n variant=\"outlined\"\n color={STATUS_COLORS[row.status]}\n label={OUTREACH_LINK_DOMAIN_STATUS_LABELS[row.status]}\n />\n </Stack>\n {row.linkPrefix ? (\n <Typography variant=\"caption\" color=\"text.secondary\" sx={{ wordBreak: 'break-all' }}>\n {`Links in email from ${row.domain} read ${row.linkPrefix}…`}\n </Typography>\n ) : null}\n {row.detail && row.status !== 'verified' ? <Alert severity=\"info\">{row.detail}</Alert> : null}\n {setUp && row.status !== 'verified' && row.records.length ? (\n <Stack spacing={1} component=\"dl\" sx={{ m: 0 }}>\n {row.records.map((record) => (\n <Box\n key={`${record.type}:${record.name}:${record.value}`}\n sx={{ border: 1, borderColor: 'divider', borderRadius: 1, p: 1.5 }}\n >\n <Typography variant=\"caption\" component=\"dt\" color=\"text.secondary\">\n {`${record.type} · ${record.name}${record.required ? '' : ' · if needed'}`}\n </Typography>\n <Typography\n variant=\"body2\"\n component=\"dd\"\n sx={{ m: 0, fontFamily: 'monospace', wordBreak: 'break-all' }}\n >\n {record.value}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {record.note}\n </Typography>\n </Box>\n ))}\n </Stack>\n ) : null}\n {setUp && row.status !== 'verified' && !row.records.length ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`Point ${row.host} at this app — a CNAME to its address, or your proxy’s — then check it.`}\n </Typography>\n ) : null}\n <Stack direction=\"row\" spacing={1} sx={{ flexWrap: 'wrap' }}>\n {!setUp && canManage ? (\n <Button\n variant=\"outlined\"\n size=\"small\"\n disabled={busy}\n aria-label={LINK_DOMAIN_LABELS.setUp(row.host)}\n onClick={() => onAction('set-up')}\n >\n Set up\n </Button>\n ) : null}\n {setUp ? (\n <Button\n variant=\"outlined\"\n size=\"small\"\n disabled={busy}\n aria-label={LINK_DOMAIN_LABELS.check(row.host)}\n onClick={() => onAction('check')}\n >\n Check\n </Button>\n ) : null}\n {setUp && canManage ? (\n <Button\n color=\"error\"\n size=\"small\"\n disabled={busy}\n aria-label={LINK_DOMAIN_LABELS.remove(row.host)}\n onClick={() => onAction('remove')}\n >\n Remove\n </Button>\n ) : null}\n </Stack>\n </Stack>\n )\n}\n\nexport default OutreachLinkDomainsCard\n"],"names":["pluginDocsHelp","CardDisplay","useConfirmationContext","useSnackbar","Alert","Box","Button","Chip","Divider","Stack","Typography","useCallback","useEffect","useState","OutreachLoading","OutreachLoadProblem","OutreachRouteError","useOutreachApi","OUTREACH_LINK_DOMAIN_STATUS_LABELS","requested","verified","failed","STATUS_COLORS","LINK_DOMAIN_LABELS","setUp","host","check","remove","OutreachLinkDomainsCard","props","orgId","api","enqueueSnackbar","confirm","load","setLoad","status","busy","setBusy","undefined","current","readLinkDomains","then","data","catch","error","refused","change","action","row","confirmed","title","description","confirmationText","confirmationButtonProps","color","domain","answer","changeLinkDomain","now","domains","find","entry","detail","variant","message","allowDuplicate","header","help","anchor","contentGutterX","contentGutterY","spacing","label","what","length","map","index","LinkDomainRow","canManage","onAction","displayName","direction","sx","alignItems","flexWrap","fontWeight","wordBreak","size","linkPrefix","severity","records","component","m","record","border","borderColor","borderRadius","p","type","name","required","fontFamily","value","note","disabled","aria-label","onClick"],"mappings":"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"}
|
|
@@ -24,6 +24,7 @@ import { useUser } from "@aglyn/tenant-feature-instance";
|
|
|
24
24
|
import { Alert, Button, Card, CircularProgress, Stack, Typography } from "@mui/material";
|
|
25
25
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
26
26
|
import { parseConnectReturnFragment } from "../mailboxes/mailbox-api.js";
|
|
27
|
+
import { OutreachLinkDomainsCard } from "./link-domains-card.js";
|
|
27
28
|
import { MailboxCard } from "./mailbox-card.js";
|
|
28
29
|
import { useOutreachMailboxApi } from "./use-outreach-mailbox-api.js";
|
|
29
30
|
import { useOutreachMailboxes } from "./use-outreach-mailboxes.js";
|
|
@@ -248,7 +249,10 @@ const leavePage = (url)=>window.location.assign(url);
|
|
|
248
249
|
onReconnect: ()=>void connect(),
|
|
249
250
|
reconnecting: connecting || !configured
|
|
250
251
|
}, mailbox.id);
|
|
251
|
-
})
|
|
252
|
+
}),
|
|
253
|
+
listed.status === 'ready' && listed.mailboxes.length ? /*#__PURE__*/ _jsx(OutreachLinkDomainsCard, {
|
|
254
|
+
orgId: orgId
|
|
255
|
+
}) : null
|
|
252
256
|
]
|
|
253
257
|
});
|
|
254
258
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/mailboxes-section.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { pluginDocsHelp } from '@aglyn/aglyn'\nimport EmptyStateComponent from '@aglyn/shared-ui-jsx/components/empty-state.component'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { Alert, Button, Card, CircularProgress, Stack, Typography } from '@mui/material'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport {\n parseConnectReturnFragment,\n type OutreachConnectReturnError,\n type OutreachMailboxAvailability,\n} from '../mailboxes/mailbox-api'\nimport { MailboxCard } from './mailbox-card'\nimport { useOutreachMailboxApi } from './use-outreach-mailbox-api'\nimport { useOutreachMailboxes } from './use-outreach-mailboxes'\n\nexport interface OutreachMailboxesSectionProps {\n /** The organization the hub is mounted under; `null` until the shell knows it. */\n orgId: string | null\n /** Sends the browser to Google's consent screen. A test seam; the page's own location by default. */\n navigate?: (url: string) => void\n}\n\nconst leavePage = (url: string) => window.location.assign(url)\n\n/** The connect button's accessible name, spelled once for the specs. */\nexport const CONNECT_WITH_GOOGLE_LABEL = 'Connect with Google'\n\n/** What Google's redirect back said, in the words the panel shows. */\nconst RETURN_ERROR_MESSAGES: Record<OutreachConnectReturnError, string> = {\n access_denied: 'Google access was not granted, so no mailbox was connected.',\n expired: 'The connection took too long. Connect the mailbox again.',\n google_error: 'Google could not complete the connection. Connect the mailbox again.',\n}\n\ntype Availability =\n | { status: 'loading' }\n | ({ status: 'ready' } & OutreachMailboxAvailability)\n | { status: 'error'; message: string }\n\ntype Returned =\n | { status: 'completing' }\n | { status: 'connected'; email: string; created: boolean; confirmedAliases: string[] }\n | { status: 'failed'; message: string }\n\n/** Takes a connect's return fragment out of the address bar without a history entry. */\nfunction stripFragment(): void {\n try {\n window.history.replaceState(null, '', window.location.pathname + window.location.search)\n } catch {\n // A restricted document: the connect still finishes.\n }\n}\n\n/** The browser's IANA timezone, for a new mailbox's sending window. */\nfunction browserTimezone(): string | undefined {\n try {\n return Intl.DateTimeFormat().resolvedOptions().timeZone || undefined\n } catch {\n return undefined\n }\n}\n\n/**\n * The Mailboxes section (AGL-2978): the organization's connected Google\n * mailboxes, and the connect that adds one.\n *\n * A connect leaves the console for Google's consent screen and comes back\n * here through the callback route, which puts the authorization code in this\n * page's URL fragment. The section reads the fragment once, takes it out of\n * the address bar before anything else, and finishes the connect with the\n * member's own session — the step that refuses a connect started by anyone\n * else.\n */\nexport function OutreachMailboxesSection(props: OutreachMailboxesSectionProps) {\n const { orgId, navigate = leavePage } = props\n const { data: user } = useUser()\n // Effects key on the uid, never on the account object: a fresh object for\n // the same person must not re-run a connect's completion or a fetch.\n const uid = user?.uid ?? null\n const api = useOutreachMailboxApi(orgId)\n const listed = useOutreachMailboxes(orgId)\n const { enqueueSnackbar } = useSnackbar()\n const [availability, setAvailability] = useState<Availability>({ status: 'loading' })\n const [returned, setReturned] = useState<Returned | null>(null)\n const [connecting, setConnecting] = useState(false)\n const returnHandled = useRef(false)\n\n // The connect's return: read once per mount, before availability, so the\n // code leaves the address bar as early as the page can take it.\n useEffect(() => {\n if (returnHandled.current || !orgId || !uid) return\n const fragment = parseConnectReturnFragment(window.location.hash)\n returnHandled.current = true\n if (!fragment) return\n stripFragment()\n if (fragment.kind === 'error') {\n setReturned({ status: 'failed', message: RETURN_ERROR_MESSAGES[fragment.reason] })\n return\n }\n setReturned({ status: 'completing' })\n api\n .complete({ code: fragment.code, state: fragment.state, timezone: browserTimezone() })\n .then((done) =>\n setReturned({\n status: 'connected',\n email: done.mailbox.email,\n created: done.created,\n confirmedAliases: done.confirmedAliases,\n }),\n )\n .catch((error: Error) => setReturned({ status: 'failed', message: error.message }))\n }, [api, orgId, uid])\n\n useEffect(() => {\n if (!orgId || !uid) return undefined\n let current = true\n setAvailability({ status: 'loading' })\n api\n .availability()\n .then((answer) => current && setAvailability({ status: 'ready', ...answer }))\n .catch((error: Error) => current && setAvailability({ status: 'error', message: error.message }))\n return () => {\n current = false\n }\n }, [api, orgId, uid])\n\n const connect = useCallback(async () => {\n setConnecting(true)\n try {\n navigate(await api.connect())\n } catch (error) {\n setConnecting(false)\n enqueueSnackbar((error as Error).message, { variant: 'error', allowDuplicate: true })\n }\n }, [api, enqueueSnackbar, navigate])\n\n const configured = availability.status === 'ready' && availability.configured\n const canManageAll = availability.status === 'ready' && availability.canManageAll\n const connectButton = (\n <Button variant=\"contained\" disabled={!configured || connecting} onClick={() => void connect()}>\n {CONNECT_WITH_GOOGLE_LABEL}\n </Button>\n )\n const mine = listed.mailboxes.filter((mailbox) => mailbox.connectedByUid === uid)\n const others = listed.mailboxes.filter((mailbox) => mailbox.connectedByUid !== uid)\n\n return (\n <Stack spacing={2}>\n <CardDisplay\n header=\"Mailboxes\"\n help={pluginDocsHelp('sequences', { anchor: '#connect-a-mailbox' })}\n contentGutterX\n contentGutterY\n HeaderProps={{ action: listed.mailboxes.length ? connectButton : undefined }}\n >\n <Stack spacing={1.5}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'A sequence sends from your own Google mailbox, so a message sits in your Sent mail ' +\n 'and a reply comes back to your inbox. Each member connects their own.'}\n </Typography>\n {availability.status === 'ready' && !availability.configured ? (\n <Alert severity=\"info\">Connecting a Google mailbox is not configured on this deployment.</Alert>\n ) : null}\n {availability.status === 'error' ? <Alert severity=\"error\">{availability.message}</Alert> : null}\n {returned?.status === 'completing' ? (\n <Alert severity=\"info\" icon={<CircularProgress size={18} />}>\n Finishing the connection…\n </Alert>\n ) : null}\n {returned?.status === 'connected' ? (\n <Alert severity=\"success\" onClose={() => setReturned(null)}>\n {`${returned.created ? 'Connected' : 'Reconnected'} ${returned.email}.` +\n (returned.confirmedAliases.length\n ? ` Verified ${returned.confirmedAliases.join(', ')} as your own.`\n : '')}\n </Alert>\n ) : null}\n {returned?.status === 'failed' ? (\n <Alert severity=\"error\" onClose={() => setReturned(null)}>\n {returned.message}\n </Alert>\n ) : null}\n </Stack>\n </CardDisplay>\n\n {listed.status === 'loading' ? (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }} role=\"status\">\n <CircularProgress size={18} />\n <Typography variant=\"body2\" color=\"text.secondary\">\n Loading mailboxes…\n </Typography>\n </Stack>\n ) : null}\n {listed.status === 'error' ? (\n <Alert severity=\"error\">The mailboxes could not be loaded. Reload the page to try again.</Alert>\n ) : null}\n {listed.status === 'ready' && !listed.mailboxes.length ? (\n <Card variant=\"outlined\">\n <EmptyStateComponent\n label=\"No mailboxes connected\"\n description={\n configured\n ? 'Connect your Google mailbox to send sequences from your own address.'\n : undefined\n }\n action={configured ? connectButton : undefined}\n />\n </Card>\n ) : null}\n {[...mine, ...others].map((mailbox) => {\n const isMine = mailbox.connectedByUid === uid\n return (\n <MailboxCard\n key={mailbox.id}\n mailbox={mailbox}\n isMine={isMine}\n canManage={isMine || canManageAll}\n api={api}\n onReconnect={() => void connect()}\n reconnecting={connecting || !configured}\n />\n )\n })}\n </Stack>\n )\n}\nOutreachMailboxesSection.displayName = 'OutreachMailboxesSection'\n\nexport default OutreachMailboxesSection\n"],"names":["pluginDocsHelp","EmptyStateComponent","CardDisplay","useSnackbar","useUser","Alert","Button","Card","CircularProgress","Stack","Typography","useCallback","useEffect","useRef","useState","parseConnectReturnFragment","MailboxCard","useOutreachMailboxApi","useOutreachMailboxes","leavePage","url","window","location","assign","CONNECT_WITH_GOOGLE_LABEL","RETURN_ERROR_MESSAGES","access_denied","expired","google_error","stripFragment","history","replaceState","pathname","search","browserTimezone","Intl","DateTimeFormat","resolvedOptions","timeZone","undefined","OutreachMailboxesSection","props","orgId","navigate","data","user","uid","api","listed","enqueueSnackbar","availability","setAvailability","status","returned","setReturned","connecting","setConnecting","returnHandled","current","fragment","hash","kind","message","reason","complete","code","state","timezone","then","done","email","mailbox","created","confirmedAliases","catch","error","answer","connect","variant","allowDuplicate","configured","canManageAll","connectButton","disabled","onClick","mine","mailboxes","filter","connectedByUid","others","spacing","header","help","anchor","contentGutterX","contentGutterY","HeaderProps","action","length","color","severity","icon","size","onClose","join","direction","sx","alignItems","role","label","description","map","isMine","canManage","onReconnect","reconnecting","id","displayName"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/mailboxes-section.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { pluginDocsHelp } from '@aglyn/aglyn'\nimport EmptyStateComponent from '@aglyn/shared-ui-jsx/components/empty-state.component'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { Alert, Button, Card, CircularProgress, Stack, Typography } from '@mui/material'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport {\n parseConnectReturnFragment,\n type OutreachConnectReturnError,\n type OutreachMailboxAvailability,\n} from '../mailboxes/mailbox-api'\nimport { OutreachLinkDomainsCard } from './link-domains-card'\nimport { MailboxCard } from './mailbox-card'\nimport { useOutreachMailboxApi } from './use-outreach-mailbox-api'\nimport { useOutreachMailboxes } from './use-outreach-mailboxes'\n\nexport interface OutreachMailboxesSectionProps {\n /** The organization the hub is mounted under; `null` until the shell knows it. */\n orgId: string | null\n /** Sends the browser to Google's consent screen. A test seam; the page's own location by default. */\n navigate?: (url: string) => void\n}\n\nconst leavePage = (url: string) => window.location.assign(url)\n\n/** The connect button's accessible name, spelled once for the specs. */\nexport const CONNECT_WITH_GOOGLE_LABEL = 'Connect with Google'\n\n/** What Google's redirect back said, in the words the panel shows. */\nconst RETURN_ERROR_MESSAGES: Record<OutreachConnectReturnError, string> = {\n access_denied: 'Google access was not granted, so no mailbox was connected.',\n expired: 'The connection took too long. Connect the mailbox again.',\n google_error: 'Google could not complete the connection. Connect the mailbox again.',\n}\n\ntype Availability =\n | { status: 'loading' }\n | ({ status: 'ready' } & OutreachMailboxAvailability)\n | { status: 'error'; message: string }\n\ntype Returned =\n | { status: 'completing' }\n | { status: 'connected'; email: string; created: boolean; confirmedAliases: string[] }\n | { status: 'failed'; message: string }\n\n/** Takes a connect's return fragment out of the address bar without a history entry. */\nfunction stripFragment(): void {\n try {\n window.history.replaceState(null, '', window.location.pathname + window.location.search)\n } catch {\n // A restricted document: the connect still finishes.\n }\n}\n\n/** The browser's IANA timezone, for a new mailbox's sending window. */\nfunction browserTimezone(): string | undefined {\n try {\n return Intl.DateTimeFormat().resolvedOptions().timeZone || undefined\n } catch {\n return undefined\n }\n}\n\n/**\n * The Mailboxes section (AGL-2978): the organization's connected Google\n * mailboxes, and the connect that adds one.\n *\n * A connect leaves the console for Google's consent screen and comes back\n * here through the callback route, which puts the authorization code in this\n * page's URL fragment. The section reads the fragment once, takes it out of\n * the address bar before anything else, and finishes the connect with the\n * member's own session — the step that refuses a connect started by anyone\n * else.\n */\nexport function OutreachMailboxesSection(props: OutreachMailboxesSectionProps) {\n const { orgId, navigate = leavePage } = props\n const { data: user } = useUser()\n // Effects key on the uid, never on the account object: a fresh object for\n // the same person must not re-run a connect's completion or a fetch.\n const uid = user?.uid ?? null\n const api = useOutreachMailboxApi(orgId)\n const listed = useOutreachMailboxes(orgId)\n const { enqueueSnackbar } = useSnackbar()\n const [availability, setAvailability] = useState<Availability>({ status: 'loading' })\n const [returned, setReturned] = useState<Returned | null>(null)\n const [connecting, setConnecting] = useState(false)\n const returnHandled = useRef(false)\n\n // The connect's return: read once per mount, before availability, so the\n // code leaves the address bar as early as the page can take it.\n useEffect(() => {\n if (returnHandled.current || !orgId || !uid) return\n const fragment = parseConnectReturnFragment(window.location.hash)\n returnHandled.current = true\n if (!fragment) return\n stripFragment()\n if (fragment.kind === 'error') {\n setReturned({ status: 'failed', message: RETURN_ERROR_MESSAGES[fragment.reason] })\n return\n }\n setReturned({ status: 'completing' })\n api\n .complete({ code: fragment.code, state: fragment.state, timezone: browserTimezone() })\n .then((done) =>\n setReturned({\n status: 'connected',\n email: done.mailbox.email,\n created: done.created,\n confirmedAliases: done.confirmedAliases,\n }),\n )\n .catch((error: Error) => setReturned({ status: 'failed', message: error.message }))\n }, [api, orgId, uid])\n\n useEffect(() => {\n if (!orgId || !uid) return undefined\n let current = true\n setAvailability({ status: 'loading' })\n api\n .availability()\n .then((answer) => current && setAvailability({ status: 'ready', ...answer }))\n .catch((error: Error) => current && setAvailability({ status: 'error', message: error.message }))\n return () => {\n current = false\n }\n }, [api, orgId, uid])\n\n const connect = useCallback(async () => {\n setConnecting(true)\n try {\n navigate(await api.connect())\n } catch (error) {\n setConnecting(false)\n enqueueSnackbar((error as Error).message, { variant: 'error', allowDuplicate: true })\n }\n }, [api, enqueueSnackbar, navigate])\n\n const configured = availability.status === 'ready' && availability.configured\n const canManageAll = availability.status === 'ready' && availability.canManageAll\n const connectButton = (\n <Button variant=\"contained\" disabled={!configured || connecting} onClick={() => void connect()}>\n {CONNECT_WITH_GOOGLE_LABEL}\n </Button>\n )\n const mine = listed.mailboxes.filter((mailbox) => mailbox.connectedByUid === uid)\n const others = listed.mailboxes.filter((mailbox) => mailbox.connectedByUid !== uid)\n\n return (\n <Stack spacing={2}>\n <CardDisplay\n header=\"Mailboxes\"\n help={pluginDocsHelp('sequences', { anchor: '#connect-a-mailbox' })}\n contentGutterX\n contentGutterY\n HeaderProps={{ action: listed.mailboxes.length ? connectButton : undefined }}\n >\n <Stack spacing={1.5}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'A sequence sends from your own Google mailbox, so a message sits in your Sent mail ' +\n 'and a reply comes back to your inbox. Each member connects their own.'}\n </Typography>\n {availability.status === 'ready' && !availability.configured ? (\n <Alert severity=\"info\">Connecting a Google mailbox is not configured on this deployment.</Alert>\n ) : null}\n {availability.status === 'error' ? <Alert severity=\"error\">{availability.message}</Alert> : null}\n {returned?.status === 'completing' ? (\n <Alert severity=\"info\" icon={<CircularProgress size={18} />}>\n Finishing the connection…\n </Alert>\n ) : null}\n {returned?.status === 'connected' ? (\n <Alert severity=\"success\" onClose={() => setReturned(null)}>\n {`${returned.created ? 'Connected' : 'Reconnected'} ${returned.email}.` +\n (returned.confirmedAliases.length\n ? ` Verified ${returned.confirmedAliases.join(', ')} as your own.`\n : '')}\n </Alert>\n ) : null}\n {returned?.status === 'failed' ? (\n <Alert severity=\"error\" onClose={() => setReturned(null)}>\n {returned.message}\n </Alert>\n ) : null}\n </Stack>\n </CardDisplay>\n\n {listed.status === 'loading' ? (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }} role=\"status\">\n <CircularProgress size={18} />\n <Typography variant=\"body2\" color=\"text.secondary\">\n Loading mailboxes…\n </Typography>\n </Stack>\n ) : null}\n {listed.status === 'error' ? (\n <Alert severity=\"error\">The mailboxes could not be loaded. Reload the page to try again.</Alert>\n ) : null}\n {listed.status === 'ready' && !listed.mailboxes.length ? (\n <Card variant=\"outlined\">\n <EmptyStateComponent\n label=\"No mailboxes connected\"\n description={\n configured\n ? 'Connect your Google mailbox to send sequences from your own address.'\n : undefined\n }\n action={configured ? connectButton : undefined}\n />\n </Card>\n ) : null}\n {[...mine, ...others].map((mailbox) => {\n const isMine = mailbox.connectedByUid === uid\n return (\n <MailboxCard\n key={mailbox.id}\n mailbox={mailbox}\n isMine={isMine}\n canManage={isMine || canManageAll}\n api={api}\n onReconnect={() => void connect()}\n reconnecting={connecting || !configured}\n />\n )\n })}\n {listed.status === 'ready' && listed.mailboxes.length ? <OutreachLinkDomainsCard orgId={orgId} /> : null}\n </Stack>\n )\n}\nOutreachMailboxesSection.displayName = 'OutreachMailboxesSection'\n\nexport default OutreachMailboxesSection\n"],"names":["pluginDocsHelp","EmptyStateComponent","CardDisplay","useSnackbar","useUser","Alert","Button","Card","CircularProgress","Stack","Typography","useCallback","useEffect","useRef","useState","parseConnectReturnFragment","OutreachLinkDomainsCard","MailboxCard","useOutreachMailboxApi","useOutreachMailboxes","leavePage","url","window","location","assign","CONNECT_WITH_GOOGLE_LABEL","RETURN_ERROR_MESSAGES","access_denied","expired","google_error","stripFragment","history","replaceState","pathname","search","browserTimezone","Intl","DateTimeFormat","resolvedOptions","timeZone","undefined","OutreachMailboxesSection","props","orgId","navigate","data","user","uid","api","listed","enqueueSnackbar","availability","setAvailability","status","returned","setReturned","connecting","setConnecting","returnHandled","current","fragment","hash","kind","message","reason","complete","code","state","timezone","then","done","email","mailbox","created","confirmedAliases","catch","error","answer","connect","variant","allowDuplicate","configured","canManageAll","connectButton","disabled","onClick","mine","mailboxes","filter","connectedByUid","others","spacing","header","help","anchor","contentGutterX","contentGutterY","HeaderProps","action","length","color","severity","icon","size","onClose","join","direction","sx","alignItems","role","label","description","map","isMine","canManage","onReconnect","reconnecting","id","displayName"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { OutreachComplianceIssue } from '../model/compliance-settings';
|
|
2
|
-
import type { OutreachDoNotContactDomainRequest, OutreachDoNotContactDomainResponse, OutreachDoNotContactDomainsResponse, OutreachEnrollmentAction, OutreachEnrollmentActionResponse, OutreachEnrollPersonRequest, OutreachEnrollPreviewResponse, OutreachEnrollResponse, OutreachEnrollSource, OutreachPreviewRequest, OutreachPreviewResponse, OutreachRouteRefusalReason, OutreachSequenceAction, OutreachSequenceSaveResponse, OutreachSequenceStatusResponse, OutreachSettingsResponse, OutreachSettingsSaveRequest, OutreachSettingsSaveResponse } from '../model/outreach-api';
|
|
2
|
+
import type { OutreachDoNotContactDomainRequest, OutreachDoNotContactDomainResponse, OutreachDoNotContactDomainsResponse, OutreachEnrollmentAction, OutreachEnrollmentActionResponse, OutreachEnrollPersonRequest, OutreachEnrollPreviewResponse, OutreachEnrollResponse, OutreachEnrollSource, OutreachLinkDomainRequest, OutreachLinkDomainResponse, OutreachLinkDomainsResponse, OutreachPreviewRequest, OutreachPreviewResponse, OutreachRouteRefusalReason, OutreachSequenceAction, OutreachSequenceSaveResponse, OutreachSequenceStatusResponse, OutreachSettingsResponse, OutreachSettingsSaveRequest, OutreachSettingsSaveResponse } from '../model/outreach-api';
|
|
3
3
|
import type { OutreachSequenceDraft, OutreachSequenceIssue } from '../model/sequence-draft';
|
|
4
4
|
/**
|
|
5
5
|
* A route's refusal, carrying its sentence, its stable reason and any
|
|
@@ -31,6 +31,10 @@ export interface OutreachApi {
|
|
|
31
31
|
readDoNotContactDomains(): Promise<OutreachDoNotContactDomainsResponse>;
|
|
32
32
|
/** Puts a domain on the list, or takes one off. */
|
|
33
33
|
changeDoNotContactDomain(action: OutreachDoNotContactDomainRequest['action'], domain: string, detail?: string): Promise<OutreachDoNotContactDomainResponse>;
|
|
34
|
+
/** Each mailbox domain's click-tracking host (AGL-3306). */
|
|
35
|
+
readLinkDomains(): Promise<OutreachLinkDomainsResponse>;
|
|
36
|
+
/** Sets a link domain up, checks it, or removes it. */
|
|
37
|
+
changeLinkDomain(action: OutreachLinkDomainRequest['action'], domain: string): Promise<OutreachLinkDomainResponse>;
|
|
34
38
|
}
|
|
35
39
|
/**
|
|
36
40
|
* The Outreach pages' one door to the settings, sequence and enrollment
|
|
@@ -142,6 +142,16 @@ import { OUTREACH_API_ROUTES } from "../constants/api-routes.js";
|
|
|
142
142
|
}, detail ? {
|
|
143
143
|
detail
|
|
144
144
|
} : {})
|
|
145
|
+
}),
|
|
146
|
+
readLinkDomains: ()=>call(OUTREACH_API_ROUTES.linkDomains, {
|
|
147
|
+
method: 'GET'
|
|
148
|
+
}),
|
|
149
|
+
changeLinkDomain: (action, domain)=>call(OUTREACH_API_ROUTES.linkDomains, {
|
|
150
|
+
method: 'POST',
|
|
151
|
+
body: {
|
|
152
|
+
action,
|
|
153
|
+
domain
|
|
154
|
+
}
|
|
145
155
|
})
|
|
146
156
|
}), [
|
|
147
157
|
call
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/use-outreach-api.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { useCallback, useMemo } from 'react'\nimport { OUTREACH_API_ROUTES } from '../constants/api-routes'\nimport type { OutreachComplianceIssue } from '../model/compliance-settings'\nimport type {\n OutreachDoNotContactDomainRequest,\n OutreachDoNotContactDomainResponse,\n OutreachDoNotContactDomainsResponse,\n OutreachEnrollmentAction,\n OutreachEnrollmentActionResponse,\n OutreachEnrollPersonRequest,\n OutreachEnrollPreviewResponse,\n OutreachEnrollResponse,\n OutreachEnrollSource,\n OutreachPreviewRequest,\n OutreachPreviewResponse,\n OutreachRouteRefusal,\n OutreachRouteRefusalReason,\n OutreachSequenceAction,\n OutreachSequenceSaveResponse,\n OutreachSequenceStatusResponse,\n OutreachSettingsResponse,\n OutreachSettingsSaveRequest,\n OutreachSettingsSaveResponse,\n} from '../model/outreach-api'\nimport type {\n OutreachSequenceDraft,\n OutreachSequenceIssue,\n} from '../model/sequence-draft'\n\n/**\n * A route's refusal, carrying its sentence, its stable reason and any\n * field-level issues. `refused` is the one a page shows as \"you can't use\n * this here\" rather than as something that went wrong: no permission, no\n * entitlement, or the release gate answering as if the route did not exist.\n */\nexport class OutreachRouteError extends Error {\n readonly reason: OutreachRouteRefusalReason | 'unreachable' | 'not-found'\n readonly status: number\n readonly issues: ReadonlyArray<\n OutreachComplianceIssue | OutreachSequenceIssue\n >\n\n constructor(\n message: string,\n reason: OutreachRouteError['reason'],\n status: number,\n issues: OutreachRouteError['issues'] = [],\n ) {\n super(message)\n this.name = 'OutreachRouteError'\n this.reason = reason\n this.status = status\n this.issues = issues\n }\n\n get refused(): boolean {\n return (\n this.reason === 'permission' ||\n this.reason === 'entitlement' ||\n this.reason === 'not-org-wide' ||\n this.reason === 'not-a-member' ||\n // The dispatcher answers a route whose plugin is not released to this\n // workspace as though it did not exist.\n this.reason === 'not-found'\n )\n }\n}\n\n/** The routes, as the Outreach pages call them. */\nexport interface OutreachApi {\n readSettings(): Promise<OutreachSettingsResponse>\n saveSettings(\n input: Omit<OutreachSettingsSaveRequest, 'orgId'>,\n ): Promise<OutreachSettingsSaveResponse>\n saveSequence(\n sequenceId: string | null,\n sequence: OutreachSequenceDraft,\n ): Promise<OutreachSequenceSaveResponse>\n setSequenceStatus(\n sequenceId: string,\n action: OutreachSequenceAction,\n ): Promise<OutreachSequenceStatusResponse>\n deleteSequence(sequenceId: string): Promise<{ ok: true }>\n previewEnrollment(\n sequenceId: string,\n source: OutreachEnrollSource,\n ): Promise<OutreachEnrollPreviewResponse>\n enroll(\n sequenceId: string,\n people: OutreachEnrollPersonRequest[],\n ): Promise<OutreachEnrollResponse>\n actOnEnrollment(\n enrollmentId: string,\n action: OutreachEnrollmentAction,\n detail?: string,\n ): Promise<OutreachEnrollmentActionResponse>\n previewEmail(\n input: Omit<OutreachPreviewRequest, 'orgId'>,\n ): Promise<OutreachPreviewResponse>\n /** The domains on the do-not-contact list (AGL-3244). */\n readDoNotContactDomains(): Promise<OutreachDoNotContactDomainsResponse>\n /** Puts a domain on the list, or takes one off. */\n changeDoNotContactDomain(\n action: OutreachDoNotContactDomainRequest['action'],\n domain: string,\n detail?: string,\n ): Promise<OutreachDoNotContactDomainResponse>\n}\n\n/**\n * The Outreach pages' one door to the settings, sequence and enrollment\n * routes (AGL-2980), with the member's session attached and the\n * organization named on every call. A refusal is thrown as an\n * {@link OutreachRouteError} carrying the route's own sentence, which is\n * written for the person reading it.\n */\nexport function useOutreachApi(orgId: string | null): OutreachApi {\n const { data: user } = useUser()\n\n const call = useCallback(\n async <T>(\n route: string,\n init: { method: 'GET' | 'POST'; body?: Record<string, unknown> },\n ): Promise<T> => {\n if (!orgId)\n throw new OutreachRouteError(\n 'Open an organization first.',\n 'org-required',\n 400,\n )\n const path =\n init.method === 'GET'\n ? `/api/${route}?orgId=${encodeURIComponent(orgId)}`\n : `/api/${route}`\n let response: Response\n try {\n response = await authorizedFetch(user, path, {\n method: init.method,\n ...(init.method === 'POST'\n ? {\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ orgId, ...init.body }),\n }\n : {}),\n })\n } catch {\n throw new OutreachRouteError(\n 'Sequences could not be reached. Try again.',\n 'unreachable',\n 0,\n )\n }\n const payload = (await response.json().catch(() => null)) as\n (T & { error?: undefined }) | Partial<OutreachRouteRefusal> | null\n if (!response.ok || !payload) {\n const refusal = (payload ?? {}) as Partial<OutreachRouteRefusal>\n throw new OutreachRouteError(\n refusal.error && refusal.reason\n ? refusal.error\n : response.status === 404\n ? \"Sequences isn't available to this workspace yet.\"\n : 'Sequences could not be reached. Try again.',\n refusal.reason ??\n (response.status === 404 ? 'not-found' : 'unreachable'),\n response.status,\n refusal.issues ?? [],\n )\n }\n return payload as T\n },\n [user, orgId],\n )\n\n return useMemo<OutreachApi>(\n () => ({\n readSettings: () => call(OUTREACH_API_ROUTES.settings, { method: 'GET' }),\n saveSettings: (input) =>\n call(OUTREACH_API_ROUTES.settings, {\n method: 'POST',\n body: { ...input },\n }),\n saveSequence: (sequenceId, sequence) =>\n call(OUTREACH_API_ROUTES.sequencesSave, {\n method: 'POST',\n body: { ...(sequenceId ? { sequenceId } : {}), sequence },\n }),\n setSequenceStatus: (sequenceId, action) =>\n call(OUTREACH_API_ROUTES.sequencesStatus, {\n method: 'POST',\n body: { sequenceId, action },\n }),\n deleteSequence: (sequenceId) =>\n call(OUTREACH_API_ROUTES.sequencesDelete, {\n method: 'POST',\n body: { sequenceId },\n }),\n previewEnrollment: (sequenceId, source) =>\n call(OUTREACH_API_ROUTES.enrollPreview, {\n method: 'POST',\n body: { sequenceId, source },\n }),\n enroll: (sequenceId, people) =>\n call(OUTREACH_API_ROUTES.enroll, {\n method: 'POST',\n body: { sequenceId, people },\n }),\n actOnEnrollment: (enrollmentId, action, detail) =>\n call(OUTREACH_API_ROUTES.enrollmentsAction, {\n method: 'POST',\n body: { enrollmentId, action, ...(detail ? { detail } : {}) },\n }),\n previewEmail: (input) =>\n call(OUTREACH_API_ROUTES.preview, {\n method: 'POST',\n body: { ...input },\n }),\n readDoNotContactDomains: () =>\n call(OUTREACH_API_ROUTES.doNotContactDomains, { method: 'GET' }),\n changeDoNotContactDomain: (action, domain, detail) =>\n call(OUTREACH_API_ROUTES.doNotContactDomains, {\n method: 'POST',\n body: { action, domain, ...(detail ? { detail } : {}) },\n }),\n }),\n [call],\n )\n}\n"],"names":["authorizedFetch","useUser","useCallback","useMemo","OUTREACH_API_ROUTES","OutreachRouteError","Error","refused","reason","message","status","issues","name","useOutreachApi","orgId","data","user","call","route","init","path","method","encodeURIComponent","response","headers","body","JSON","stringify","payload","json","catch","ok","refusal","error","readSettings","settings","saveSettings","input","saveSequence","sequenceId","sequence","sequencesSave","setSequenceStatus","action","sequencesStatus","deleteSequence","sequencesDelete","previewEnrollment","source","enrollPreview","enroll","people","actOnEnrollment","enrollmentId","detail","enrollmentsAction","previewEmail","preview","readDoNotContactDomains","doNotContactDomains","changeDoNotContactDomain","domain"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/use-outreach-api.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { useCallback, useMemo } from 'react'\nimport { OUTREACH_API_ROUTES } from '../constants/api-routes'\nimport type { OutreachComplianceIssue } from '../model/compliance-settings'\nimport type {\n OutreachDoNotContactDomainRequest,\n OutreachDoNotContactDomainResponse,\n OutreachDoNotContactDomainsResponse,\n OutreachEnrollmentAction,\n OutreachEnrollmentActionResponse,\n OutreachEnrollPersonRequest,\n OutreachEnrollPreviewResponse,\n OutreachEnrollResponse,\n OutreachEnrollSource,\n OutreachLinkDomainRequest,\n OutreachLinkDomainResponse,\n OutreachLinkDomainsResponse,\n OutreachPreviewRequest,\n OutreachPreviewResponse,\n OutreachRouteRefusal,\n OutreachRouteRefusalReason,\n OutreachSequenceAction,\n OutreachSequenceSaveResponse,\n OutreachSequenceStatusResponse,\n OutreachSettingsResponse,\n OutreachSettingsSaveRequest,\n OutreachSettingsSaveResponse,\n} from '../model/outreach-api'\nimport type {\n OutreachSequenceDraft,\n OutreachSequenceIssue,\n} from '../model/sequence-draft'\n\n/**\n * A route's refusal, carrying its sentence, its stable reason and any\n * field-level issues. `refused` is the one a page shows as \"you can't use\n * this here\" rather than as something that went wrong: no permission, no\n * entitlement, or the release gate answering as if the route did not exist.\n */\nexport class OutreachRouteError extends Error {\n readonly reason: OutreachRouteRefusalReason | 'unreachable' | 'not-found'\n readonly status: number\n readonly issues: ReadonlyArray<\n OutreachComplianceIssue | OutreachSequenceIssue\n >\n\n constructor(\n message: string,\n reason: OutreachRouteError['reason'],\n status: number,\n issues: OutreachRouteError['issues'] = [],\n ) {\n super(message)\n this.name = 'OutreachRouteError'\n this.reason = reason\n this.status = status\n this.issues = issues\n }\n\n get refused(): boolean {\n return (\n this.reason === 'permission' ||\n this.reason === 'entitlement' ||\n this.reason === 'not-org-wide' ||\n this.reason === 'not-a-member' ||\n // The dispatcher answers a route whose plugin is not released to this\n // workspace as though it did not exist.\n this.reason === 'not-found'\n )\n }\n}\n\n/** The routes, as the Outreach pages call them. */\nexport interface OutreachApi {\n readSettings(): Promise<OutreachSettingsResponse>\n saveSettings(\n input: Omit<OutreachSettingsSaveRequest, 'orgId'>,\n ): Promise<OutreachSettingsSaveResponse>\n saveSequence(\n sequenceId: string | null,\n sequence: OutreachSequenceDraft,\n ): Promise<OutreachSequenceSaveResponse>\n setSequenceStatus(\n sequenceId: string,\n action: OutreachSequenceAction,\n ): Promise<OutreachSequenceStatusResponse>\n deleteSequence(sequenceId: string): Promise<{ ok: true }>\n previewEnrollment(\n sequenceId: string,\n source: OutreachEnrollSource,\n ): Promise<OutreachEnrollPreviewResponse>\n enroll(\n sequenceId: string,\n people: OutreachEnrollPersonRequest[],\n ): Promise<OutreachEnrollResponse>\n actOnEnrollment(\n enrollmentId: string,\n action: OutreachEnrollmentAction,\n detail?: string,\n ): Promise<OutreachEnrollmentActionResponse>\n previewEmail(\n input: Omit<OutreachPreviewRequest, 'orgId'>,\n ): Promise<OutreachPreviewResponse>\n /** The domains on the do-not-contact list (AGL-3244). */\n readDoNotContactDomains(): Promise<OutreachDoNotContactDomainsResponse>\n /** Puts a domain on the list, or takes one off. */\n changeDoNotContactDomain(\n action: OutreachDoNotContactDomainRequest['action'],\n domain: string,\n detail?: string,\n ): Promise<OutreachDoNotContactDomainResponse>\n /** Each mailbox domain's click-tracking host (AGL-3306). */\n readLinkDomains(): Promise<OutreachLinkDomainsResponse>\n /** Sets a link domain up, checks it, or removes it. */\n changeLinkDomain(\n action: OutreachLinkDomainRequest['action'],\n domain: string,\n ): Promise<OutreachLinkDomainResponse>\n}\n\n/**\n * The Outreach pages' one door to the settings, sequence and enrollment\n * routes (AGL-2980), with the member's session attached and the\n * organization named on every call. A refusal is thrown as an\n * {@link OutreachRouteError} carrying the route's own sentence, which is\n * written for the person reading it.\n */\nexport function useOutreachApi(orgId: string | null): OutreachApi {\n const { data: user } = useUser()\n\n const call = useCallback(\n async <T>(\n route: string,\n init: { method: 'GET' | 'POST'; body?: Record<string, unknown> },\n ): Promise<T> => {\n if (!orgId)\n throw new OutreachRouteError(\n 'Open an organization first.',\n 'org-required',\n 400,\n )\n const path =\n init.method === 'GET'\n ? `/api/${route}?orgId=${encodeURIComponent(orgId)}`\n : `/api/${route}`\n let response: Response\n try {\n response = await authorizedFetch(user, path, {\n method: init.method,\n ...(init.method === 'POST'\n ? {\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ orgId, ...init.body }),\n }\n : {}),\n })\n } catch {\n throw new OutreachRouteError(\n 'Sequences could not be reached. Try again.',\n 'unreachable',\n 0,\n )\n }\n const payload = (await response.json().catch(() => null)) as\n (T & { error?: undefined }) | Partial<OutreachRouteRefusal> | null\n if (!response.ok || !payload) {\n const refusal = (payload ?? {}) as Partial<OutreachRouteRefusal>\n throw new OutreachRouteError(\n refusal.error && refusal.reason\n ? refusal.error\n : response.status === 404\n ? \"Sequences isn't available to this workspace yet.\"\n : 'Sequences could not be reached. Try again.',\n refusal.reason ??\n (response.status === 404 ? 'not-found' : 'unreachable'),\n response.status,\n refusal.issues ?? [],\n )\n }\n return payload as T\n },\n [user, orgId],\n )\n\n return useMemo<OutreachApi>(\n () => ({\n readSettings: () => call(OUTREACH_API_ROUTES.settings, { method: 'GET' }),\n saveSettings: (input) =>\n call(OUTREACH_API_ROUTES.settings, {\n method: 'POST',\n body: { ...input },\n }),\n saveSequence: (sequenceId, sequence) =>\n call(OUTREACH_API_ROUTES.sequencesSave, {\n method: 'POST',\n body: { ...(sequenceId ? { sequenceId } : {}), sequence },\n }),\n setSequenceStatus: (sequenceId, action) =>\n call(OUTREACH_API_ROUTES.sequencesStatus, {\n method: 'POST',\n body: { sequenceId, action },\n }),\n deleteSequence: (sequenceId) =>\n call(OUTREACH_API_ROUTES.sequencesDelete, {\n method: 'POST',\n body: { sequenceId },\n }),\n previewEnrollment: (sequenceId, source) =>\n call(OUTREACH_API_ROUTES.enrollPreview, {\n method: 'POST',\n body: { sequenceId, source },\n }),\n enroll: (sequenceId, people) =>\n call(OUTREACH_API_ROUTES.enroll, {\n method: 'POST',\n body: { sequenceId, people },\n }),\n actOnEnrollment: (enrollmentId, action, detail) =>\n call(OUTREACH_API_ROUTES.enrollmentsAction, {\n method: 'POST',\n body: { enrollmentId, action, ...(detail ? { detail } : {}) },\n }),\n previewEmail: (input) =>\n call(OUTREACH_API_ROUTES.preview, {\n method: 'POST',\n body: { ...input },\n }),\n readDoNotContactDomains: () =>\n call(OUTREACH_API_ROUTES.doNotContactDomains, { method: 'GET' }),\n changeDoNotContactDomain: (action, domain, detail) =>\n call(OUTREACH_API_ROUTES.doNotContactDomains, {\n method: 'POST',\n body: { action, domain, ...(detail ? { detail } : {}) },\n }),\n readLinkDomains: () => call(OUTREACH_API_ROUTES.linkDomains, { method: 'GET' }),\n changeLinkDomain: (action, domain) =>\n call(OUTREACH_API_ROUTES.linkDomains, {\n method: 'POST',\n body: { action, domain },\n }),\n }),\n [call],\n )\n}\n"],"names":["authorizedFetch","useUser","useCallback","useMemo","OUTREACH_API_ROUTES","OutreachRouteError","Error","refused","reason","message","status","issues","name","useOutreachApi","orgId","data","user","call","route","init","path","method","encodeURIComponent","response","headers","body","JSON","stringify","payload","json","catch","ok","refusal","error","readSettings","settings","saveSettings","input","saveSequence","sequenceId","sequence","sequencesSave","setSequenceStatus","action","sequencesStatus","deleteSequence","sequencesDelete","previewEnrollment","source","enrollPreview","enroll","people","actOnEnrollment","enrollmentId","detail","enrollmentsAction","previewEmail","preview","readDoNotContactDomains","doNotContactDomains","changeDoNotContactDomain","domain","readLinkDomains","linkDomains","changeLinkDomain"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;AAEA,SAASA,eAAe,QAAQ,2CAA0C;AAC1E,SAASC,OAAO,QAAQ,iCAAgC;AACxD,SAASC,WAAW,EAAEC,OAAO,QAAQ,QAAO;AAC5C,SAASC,mBAAmB,QAAQ,6BAAyB;AA+B7D;;;;;CAKC,GACD,OAAO,MAAMC,2BAA2BC;IAoBtC,IAAIC,UAAmB;QACrB,OACE,IAAI,CAACC,MAAM,KAAK,gBAChB,IAAI,CAACA,MAAM,KAAK,iBAChB,IAAI,CAACA,MAAM,KAAK,kBAChB,IAAI,CAACA,MAAM,KAAK,kBAChB,sEAAsE;QACtE,wCAAwC;QACxC,IAAI,CAACA,MAAM,KAAK;IAEpB;IAvBA,YACEC,OAAe,EACfD,MAAoC,EACpCE,MAAc,EACdC,SAAuC,EAAE,CACzC;QACA,KAAK,CAACF;QACN,IAAI,CAACG,IAAI,GAAG;QACZ,IAAI,CAACJ,MAAM,GAAGA;QACd,IAAI,CAACE,MAAM,GAAGA;QACd,IAAI,CAACC,MAAM,GAAGA;IAChB;AAaF;AAkDA;;;;;;CAMC,GACD,OAAO,SAASE,eAAeC,KAAoB;IACjD,MAAM,EAAEC,MAAMC,IAAI,EAAE,GAAGf;IAEvB,MAAMgB,OAAOf,YACX,OACEgB,OACAC;QAEA,IAAI,CAACL,OACH,MAAM,IAAIT,mBACR,+BACA,gBACA;QAEJ,MAAMe,OACJD,KAAKE,MAAM,KAAK,QACZ,CAAC,KAAK,EAAEH,MAAM,OAAO,EAAEI,mBAAmBR,QAAQ,GAClD,CAAC,KAAK,EAAEI,OAAO;QACrB,IAAIK;QACJ,IAAI;YACFA,WAAW,MAAMvB,gBAAgBgB,MAAMI,MAAM;gBAC3CC,QAAQF,KAAKE,MAAM;eACfF,KAAKE,MAAM,KAAK,SAChB;gBACEG,SAAS;oBAAE,gBAAgB;gBAAmB;gBAC9CC,MAAMC,KAAKC,SAAS,CAAC;oBAAEb;mBAAUK,KAAKM,IAAI;YAC5C,IACA,CAAC;QAET,EAAE,eAAM;YACN,MAAM,IAAIpB,mBACR,8CACA,eACA;QAEJ;QACA,MAAMuB,UAAW,MAAML,SAASM,IAAI,GAAGC,KAAK,CAAC,IAAM;QAEnD,IAAI,CAACP,SAASQ,EAAE,IAAI,CAACH,SAAS;gBAQ1BI,iBAGAA;YAVF,MAAMA,UAAWJ,kBAAAA,UAAW,CAAC;YAC7B,MAAM,IAAIvB,mBACR2B,QAAQC,KAAK,IAAID,QAAQxB,MAAM,GAC3BwB,QAAQC,KAAK,GACbV,SAASb,MAAM,KAAK,MAClB,qDACA,+CACNsB,kBAAAA,QAAQxB,MAAM,YAAdwB,kBACGT,SAASb,MAAM,KAAK,MAAM,cAAc,eAC3Ca,SAASb,MAAM,GACfsB,kBAAAA,QAAQrB,MAAM,YAAdqB,kBAAkB,EAAE;QAExB;QACA,OAAOJ;IACT,GACA;QAACZ;QAAMF;KAAM;IAGf,OAAOX,QACL,IAAO,CAAA;YACL+B,cAAc,IAAMjB,KAAKb,oBAAoB+B,QAAQ,EAAE;oBAAEd,QAAQ;gBAAM;YACvEe,cAAc,CAACC,QACbpB,KAAKb,oBAAoB+B,QAAQ,EAAE;oBACjCd,QAAQ;oBACRI,MAAM,aAAKY;gBACb;YACFC,cAAc,CAACC,YAAYC,WACzBvB,KAAKb,oBAAoBqC,aAAa,EAAE;oBACtCpB,QAAQ;oBACRI,MAAM,aAAMc,aAAa;wBAAEA;oBAAW,IAAI,CAAC;wBAAIC;;gBACjD;YACFE,mBAAmB,CAACH,YAAYI,SAC9B1B,KAAKb,oBAAoBwC,eAAe,EAAE;oBACxCvB,QAAQ;oBACRI,MAAM;wBAAEc;wBAAYI;oBAAO;gBAC7B;YACFE,gBAAgB,CAACN,aACftB,KAAKb,oBAAoB0C,eAAe,EAAE;oBACxCzB,QAAQ;oBACRI,MAAM;wBAAEc;oBAAW;gBACrB;YACFQ,mBAAmB,CAACR,YAAYS,SAC9B/B,KAAKb,oBAAoB6C,aAAa,EAAE;oBACtC5B,QAAQ;oBACRI,MAAM;wBAAEc;wBAAYS;oBAAO;gBAC7B;YACFE,QAAQ,CAACX,YAAYY,SACnBlC,KAAKb,oBAAoB8C,MAAM,EAAE;oBAC/B7B,QAAQ;oBACRI,MAAM;wBAAEc;wBAAYY;oBAAO;gBAC7B;YACFC,iBAAiB,CAACC,cAAcV,QAAQW,SACtCrC,KAAKb,oBAAoBmD,iBAAiB,EAAE;oBAC1ClC,QAAQ;oBACRI,MAAM;wBAAE4B;wBAAcV;uBAAYW,SAAS;wBAAEA;oBAAO,IAAI,CAAC;gBAC3D;YACFE,cAAc,CAACnB,QACbpB,KAAKb,oBAAoBqD,OAAO,EAAE;oBAChCpC,QAAQ;oBACRI,MAAM,aAAKY;gBACb;YACFqB,yBAAyB,IACvBzC,KAAKb,oBAAoBuD,mBAAmB,EAAE;oBAAEtC,QAAQ;gBAAM;YAChEuC,0BAA0B,CAACjB,QAAQkB,QAAQP,SACzCrC,KAAKb,oBAAoBuD,mBAAmB,EAAE;oBAC5CtC,QAAQ;oBACRI,MAAM;wBAAEkB;wBAAQkB;uBAAYP,SAAS;wBAAEA;oBAAO,IAAI,CAAC;gBACrD;YACFQ,iBAAiB,IAAM7C,KAAKb,oBAAoB2D,WAAW,EAAE;oBAAE1C,QAAQ;gBAAM;YAC7E2C,kBAAkB,CAACrB,QAAQkB,SACzB5C,KAAKb,oBAAoB2D,WAAW,EAAE;oBACpC1C,QAAQ;oBACRI,MAAM;wBAAEkB;wBAAQkB;oBAAO;gBACzB;QACJ,CAAA,GACA;QAAC5C;KAAK;AAEV"}
|