@aglyn/plugins-outreach 1.0.0-beta.200 → 1.0.0-beta.202
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/package.json +14 -14
- package/src/lib/components/enrollment-actions.d.ts +33 -0
- package/src/lib/components/enrollment-actions.js +174 -0
- package/src/lib/components/enrollment-actions.js.map +1 -0
- package/src/lib/components/enrollment-detail.d.ts +21 -0
- package/src/lib/components/enrollment-detail.js +870 -0
- package/src/lib/components/enrollment-detail.js.map +1 -0
- package/src/lib/components/enrollment-filter-params.d.ts +58 -0
- package/src/lib/components/enrollment-filter-params.js +98 -0
- package/src/lib/components/enrollment-filter-params.js.map +1 -0
- package/src/lib/components/enrollments-table.d.ts +21 -0
- package/src/lib/components/enrollments-table.js +249 -210
- package/src/lib/components/enrollments-table.js.map +1 -1
- package/src/lib/components/outreach-console-page.js +10 -0
- package/src/lib/components/outreach-console-page.js.map +1 -1
- package/src/lib/components/sequence-detail.d.ts +2 -0
- package/src/lib/components/sequence-detail.js +70 -8
- package/src/lib/components/sequence-detail.js.map +1 -1
- package/src/lib/components/sequence-report-card.d.ts +17 -0
- package/src/lib/components/sequence-report-card.js +63 -15
- package/src/lib/components/sequence-report-card.js.map +1 -1
- package/src/lib/components/sequences-section.d.ts +4 -1
- package/src/lib/components/sequences-section.js +12 -1
- package/src/lib/components/sequences-section.js.map +1 -1
- package/src/lib/components/use-outreach-api.d.ts +3 -1
- package/src/lib/components/use-outreach-api.js +6 -0
- package/src/lib/components/use-outreach-api.js.map +1 -1
- package/src/lib/components/use-outreach-enrollment.d.ts +18 -0
- package/src/lib/components/use-outreach-enrollment.js +133 -0
- package/src/lib/components/use-outreach-enrollment.js.map +1 -0
- package/src/lib/constants/api-routes.d.ts +2 -0
- package/src/lib/constants/api-routes.js +1 -0
- package/src/lib/constants/api-routes.js.map +1 -1
- package/src/lib/engine/gates.d.ts +4 -3
- package/src/lib/engine/gates.js.map +1 -1
- package/src/lib/engine/gateway-block.d.ts +4 -8
- package/src/lib/engine/gateway-block.js +6 -26
- package/src/lib/engine/gateway-block.js.map +1 -1
- package/src/lib/engine/mail-gateway.d.ts +18 -73
- package/src/lib/engine/mail-gateway.js +32 -206
- package/src/lib/engine/mail-gateway.js.map +1 -1
- package/src/lib/model/enrollment-engagement.d.ts +26 -2
- package/src/lib/model/enrollment-engagement.js +40 -3
- package/src/lib/model/enrollment-engagement.js.map +1 -1
- package/src/lib/model/enrollment-history.d.ts +40 -0
- package/src/lib/model/enrollment-history.js +95 -0
- package/src/lib/model/enrollment-history.js.map +1 -0
- package/src/lib/model/enrollment-list-query.d.ts +6 -0
- package/src/lib/model/enrollment-list-query.js +35 -1
- package/src/lib/model/enrollment-list-query.js.map +1 -1
- package/src/lib/model/enrollment-timeline.d.ts +78 -0
- package/src/lib/model/enrollment-timeline.js +430 -0
- package/src/lib/model/enrollment-timeline.js.map +1 -0
- package/src/lib/model/outreach-api.d.ts +24 -0
- package/src/lib/model/outreach-api.js.map +1 -1
- package/src/lib/model/outreach.types.d.ts +78 -29
- package/src/lib/model/outreach.types.js +33 -8
- package/src/lib/model/outreach.types.js.map +1 -1
- package/src/lib/routes/enroll-routes.js +26 -3
- package/src/lib/routes/enroll-routes.js.map +1 -1
- package/src/lib/routes/enrollment-routes.d.ts +10 -0
- package/src/lib/routes/enrollment-routes.js +82 -1
- package/src/lib/routes/enrollment-routes.js.map +1 -1
- package/src/lib/routes/register-routes.js +14 -4
- package/src/lib/routes/register-routes.js.map +1 -1
- package/src/lib/routes/route-deps.d.ts +3 -1
- package/src/lib/routes/route-deps.js.map +1 -1
- package/src/lib/runtime/click-events.d.ts +10 -5
- package/src/lib/runtime/click-events.js +45 -6
- package/src/lib/runtime/click-events.js.map +1 -1
- package/src/lib/runtime/gateway-ledger.d.ts +6 -0
- package/src/lib/runtime/gateway-ledger.js +9 -3
- package/src/lib/runtime/gateway-ledger.js.map +1 -1
- package/src/lib/runtime/host-suspension.d.ts +30 -0
- package/src/lib/runtime/host-suspension.js +35 -0
- package/src/lib/runtime/host-suspension.js.map +1 -0
- package/src/lib/runtime/person-erasure.d.ts +4 -1
- package/src/lib/runtime/person-erasure.js +14 -3
- package/src/lib/runtime/person-erasure.js.map +1 -1
- package/src/lib/runtime/platform-runtime-deps.js +34 -3
- package/src/lib/runtime/platform-runtime-deps.js.map +1 -1
- package/src/lib/runtime/runtime-deps.d.ts +27 -1
- package/src/lib/runtime/runtime-deps.js.map +1 -1
- package/src/lib/runtime/send-job.js +46 -6
- package/src/lib/runtime/send-job.js.map +1 -1
- package/src/lib/runtime/sync-job.js +4 -0
- package/src/lib/runtime/sync-job.js.map +1 -1
- package/src/lib/storage/domain-intel-store.d.ts +61 -43
- package/src/lib/storage/domain-intel-store.js +138 -226
- package/src/lib/storage/domain-intel-store.js.map +1 -1
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.202",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"homepage": "https://aglyn.com",
|
|
6
6
|
"repository": {
|
|
@@ -25,19 +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-email": "1.0.0-beta.
|
|
35
|
-
"@aglyn/shared-util-http": "1.0.0-beta.
|
|
36
|
-
"@aglyn/shared-util-timestamp": "1.0.0-beta.
|
|
37
|
-
"@aglyn/shared-util-tools": "1.0.0-beta.
|
|
38
|
-
"@aglyn/tenant-data-admin": "1.0.0-beta.
|
|
39
|
-
"@aglyn/tenant-feature-instance": "1.0.0-beta.
|
|
40
|
-
"@aglyn/tenant-runtime": "1.0.0-beta.
|
|
28
|
+
"@aglyn/aglyn": "1.0.0-beta.202",
|
|
29
|
+
"@aglyn/shared-data-mdi": "1.0.0-beta.202",
|
|
30
|
+
"@aglyn/shared-ui-email-campaigns": "1.0.0-beta.202",
|
|
31
|
+
"@aglyn/shared-ui-jsx": "1.0.0-beta.202",
|
|
32
|
+
"@aglyn/shared-ui-next": "1.0.0-beta.202",
|
|
33
|
+
"@aglyn/shared-ui-snackstack": "1.0.0-beta.202",
|
|
34
|
+
"@aglyn/shared-util-email": "1.0.0-beta.202",
|
|
35
|
+
"@aglyn/shared-util-http": "1.0.0-beta.202",
|
|
36
|
+
"@aglyn/shared-util-timestamp": "1.0.0-beta.202",
|
|
37
|
+
"@aglyn/shared-util-tools": "1.0.0-beta.202",
|
|
38
|
+
"@aglyn/tenant-data-admin": "1.0.0-beta.202",
|
|
39
|
+
"@aglyn/tenant-feature-instance": "1.0.0-beta.202",
|
|
40
|
+
"@aglyn/tenant-runtime": "1.0.0-beta.202",
|
|
41
41
|
"@swc/helpers": "0.5.23"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { RowActionsMenuItem } from '@aglyn/shared-ui-jsx/components/row-actions-menu.component';
|
|
2
|
+
import { type ReactNode } from 'react';
|
|
3
|
+
import type { OutreachEnrollmentAction } from '../model/outreach-api';
|
|
4
|
+
import type { OutreachEnrollment, OutreachSequenceStep } from '../model/outreach.types';
|
|
5
|
+
import type { OutreachApi } from './use-outreach-api';
|
|
6
|
+
/**
|
|
7
|
+
* WHAT A MEMBER CAN DO TO ONE ENROLLMENT (AGL-2980, AGL-3324), in one place
|
|
8
|
+
* for the two places it is offered: a row's menu on the Enrollments tab, and
|
|
9
|
+
* the header of the person's own page (AGL-3332). The same actions, the same
|
|
10
|
+
* words, the same two confirmations — a Stop that asked first in one place
|
|
11
|
+
* and not in the other would be two products.
|
|
12
|
+
*/
|
|
13
|
+
/** The four the action route takes, and curating the next step (AGL-3324). */
|
|
14
|
+
export type OutreachEnrollmentRowAction = OutreachEnrollmentAction | 'curate';
|
|
15
|
+
/** What a member may do to an enrollment in the status it is in. */
|
|
16
|
+
export declare function outreachEnrollmentActionsFor(enrollment: OutreachEnrollment, steps: readonly OutreachSequenceStep[]): OutreachEnrollmentRowAction[];
|
|
17
|
+
export declare const OUTREACH_ENROLLMENT_ACTION_LABELS: Record<OutreachEnrollmentRowAction, string>;
|
|
18
|
+
/** The two actions that end something for good. */
|
|
19
|
+
export declare const OUTREACH_DESTRUCTIVE_ENROLLMENT_ACTIONS: readonly OutreachEnrollmentRowAction[];
|
|
20
|
+
export interface OutreachEnrollmentActions {
|
|
21
|
+
/** The enrollment an action is running on, if any. */
|
|
22
|
+
busy: string | null;
|
|
23
|
+
/** Runs one action: straight away, after its confirmation, or in the curate dialog. */
|
|
24
|
+
choose(enrollment: OutreachEnrollment, action: OutreachEnrollmentRowAction): void;
|
|
25
|
+
/** The actions as menu items, the destructive ones marked. */
|
|
26
|
+
menuItems(enrollment: OutreachEnrollment, only?: (action: OutreachEnrollmentRowAction) => boolean): RowActionsMenuItem[];
|
|
27
|
+
/** The confirmation and the curate dialog, rendered once wherever the actions are offered. */
|
|
28
|
+
dialogs: ReactNode;
|
|
29
|
+
}
|
|
30
|
+
export declare function useOutreachEnrollmentActions(input: {
|
|
31
|
+
api: OutreachApi;
|
|
32
|
+
steps: readonly OutreachSequenceStep[];
|
|
33
|
+
}): OutreachEnrollmentActions;
|
|
@@ -0,0 +1,174 @@
|
|
|
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, Fragment as _Fragment } from "react/jsx-runtime";
|
|
18
|
+
import { useSnackbar } from "@aglyn/shared-ui-snackstack";
|
|
19
|
+
import { Button, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Stack, TextField } from "@mui/material";
|
|
20
|
+
import { useState } from "react";
|
|
21
|
+
import { OutreachCurateStepDialog, outreachNextEmailStepIndex } from "./curate-step-dialog.js";
|
|
22
|
+
/** What a member may do to an enrollment in the status it is in. */ export function outreachEnrollmentActionsFor(enrollment, steps) {
|
|
23
|
+
const actions = [];
|
|
24
|
+
// Rewrite the next email for this one person, while one is still to go.
|
|
25
|
+
if (outreachNextEmailStepIndex(enrollment, steps) !== null) actions.push('curate');
|
|
26
|
+
if (enrollment.status === 'active') actions.push('pause', 'stop');
|
|
27
|
+
if (enrollment.status === 'paused') actions.push('resume', 'stop');
|
|
28
|
+
if (enrollment.status !== 'opted_out') actions.push('do_not_contact');
|
|
29
|
+
return actions;
|
|
30
|
+
}
|
|
31
|
+
export const OUTREACH_ENROLLMENT_ACTION_LABELS = {
|
|
32
|
+
curate: 'Curate next step',
|
|
33
|
+
pause: 'Pause',
|
|
34
|
+
resume: 'Resume',
|
|
35
|
+
stop: 'Stop',
|
|
36
|
+
do_not_contact: 'Mark do-not-contact'
|
|
37
|
+
};
|
|
38
|
+
/** The two actions that end something for good. */ export const OUTREACH_DESTRUCTIVE_ENROLLMENT_ACTIONS = [
|
|
39
|
+
'stop',
|
|
40
|
+
'do_not_contact'
|
|
41
|
+
];
|
|
42
|
+
/** What the snackbar says once an action lands. */ const DONE_LABELS = {
|
|
43
|
+
pause: 'Paused.',
|
|
44
|
+
resume: 'Resumed.',
|
|
45
|
+
stop: 'Stopped.'
|
|
46
|
+
};
|
|
47
|
+
/** The two actions that end something for good ask first. */ const CONFIRM = {
|
|
48
|
+
stop: {
|
|
49
|
+
title: 'Stop this enrollment?',
|
|
50
|
+
body: 'They won’t get any more steps from this sequence, and they can’t be enrolled in it again.',
|
|
51
|
+
label: 'Stop'
|
|
52
|
+
},
|
|
53
|
+
do_not_contact: {
|
|
54
|
+
title: 'Mark do-not-contact?',
|
|
55
|
+
body: 'The address goes on your organization’s do-not-contact list, which every sequence checks before ' + 'every send, and they are stopped in every sequence they are in.',
|
|
56
|
+
label: 'Mark do-not-contact'
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
export function useOutreachEnrollmentActions(input) {
|
|
60
|
+
var _CONFIRM_confirming_action, _CONFIRM_confirming_action1, _CONFIRM_confirming_action2;
|
|
61
|
+
const { api, steps } = input;
|
|
62
|
+
const { enqueueSnackbar } = useSnackbar();
|
|
63
|
+
const [confirming, setConfirming] = useState(null);
|
|
64
|
+
const [detail, setDetail] = useState('');
|
|
65
|
+
const [busy, setBusy] = useState(null);
|
|
66
|
+
const [curating, setCurating] = useState(null);
|
|
67
|
+
const act = async (enrollment, action, why)=>{
|
|
68
|
+
setBusy(enrollment.id);
|
|
69
|
+
try {
|
|
70
|
+
const answer = await api.actOnEnrollment(enrollment.id, action, why);
|
|
71
|
+
const others = answer.stoppedOthers;
|
|
72
|
+
enqueueSnackbar(action === 'do_not_contact' ? `Marked do-not-contact${others ? `, and stopped in ${others} other ${others === 1 ? 'sequence' : 'sequences'}` : ''}.` : answer.changed ? DONE_LABELS[action] : 'Nothing to change.', {
|
|
73
|
+
variant: 'success'
|
|
74
|
+
});
|
|
75
|
+
} catch (error) {
|
|
76
|
+
enqueueSnackbar(error.message, {
|
|
77
|
+
variant: 'error',
|
|
78
|
+
allowDuplicate: true
|
|
79
|
+
});
|
|
80
|
+
} finally{
|
|
81
|
+
setBusy(null);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
const choose = (enrollment, action)=>{
|
|
85
|
+
if (action === 'curate') {
|
|
86
|
+
setCurating(enrollment);
|
|
87
|
+
} else if (CONFIRM[action]) {
|
|
88
|
+
setDetail('');
|
|
89
|
+
setConfirming({
|
|
90
|
+
enrollment,
|
|
91
|
+
action
|
|
92
|
+
});
|
|
93
|
+
} else {
|
|
94
|
+
void act(enrollment, action);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
const menuItems = (enrollment, only)=>outreachEnrollmentActionsFor(enrollment, steps).filter((action)=>!only || only(action)).map((action)=>({
|
|
98
|
+
key: action,
|
|
99
|
+
label: OUTREACH_ENROLLMENT_ACTION_LABELS[action],
|
|
100
|
+
onClick: ()=>choose(enrollment, action),
|
|
101
|
+
destructive: OUTREACH_DESTRUCTIVE_ENROLLMENT_ACTIONS.includes(action),
|
|
102
|
+
disabled: busy === enrollment.id
|
|
103
|
+
}));
|
|
104
|
+
const dialogs = /*#__PURE__*/ _jsxs(_Fragment, {
|
|
105
|
+
children: [
|
|
106
|
+
/*#__PURE__*/ _jsx(OutreachCurateStepDialog, {
|
|
107
|
+
enrollment: curating,
|
|
108
|
+
steps: steps,
|
|
109
|
+
api: api,
|
|
110
|
+
onClose: ()=>setCurating(null)
|
|
111
|
+
}),
|
|
112
|
+
/*#__PURE__*/ _jsx(Dialog, {
|
|
113
|
+
open: Boolean(confirming),
|
|
114
|
+
onClose: ()=>setConfirming(null),
|
|
115
|
+
fullWidth: true,
|
|
116
|
+
maxWidth: "xs",
|
|
117
|
+
children: confirming ? /*#__PURE__*/ _jsxs(_Fragment, {
|
|
118
|
+
children: [
|
|
119
|
+
/*#__PURE__*/ _jsx(DialogTitle, {
|
|
120
|
+
children: (_CONFIRM_confirming_action = CONFIRM[confirming.action]) == null ? void 0 : _CONFIRM_confirming_action.title
|
|
121
|
+
}),
|
|
122
|
+
/*#__PURE__*/ _jsx(DialogContent, {
|
|
123
|
+
children: /*#__PURE__*/ _jsxs(Stack, {
|
|
124
|
+
spacing: 2,
|
|
125
|
+
children: [
|
|
126
|
+
/*#__PURE__*/ _jsx(DialogContentText, {
|
|
127
|
+
children: (_CONFIRM_confirming_action1 = CONFIRM[confirming.action]) == null ? void 0 : _CONFIRM_confirming_action1.body
|
|
128
|
+
}),
|
|
129
|
+
/*#__PURE__*/ _jsx(TextField, {
|
|
130
|
+
label: "Why (optional)",
|
|
131
|
+
value: detail,
|
|
132
|
+
onChange: (event)=>setDetail(event.target.value),
|
|
133
|
+
fullWidth: true,
|
|
134
|
+
slotProps: {
|
|
135
|
+
htmlInput: {
|
|
136
|
+
maxLength: 200
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
})
|
|
140
|
+
]
|
|
141
|
+
})
|
|
142
|
+
}),
|
|
143
|
+
/*#__PURE__*/ _jsxs(DialogActions, {
|
|
144
|
+
children: [
|
|
145
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
146
|
+
onClick: ()=>setConfirming(null),
|
|
147
|
+
children: "Cancel"
|
|
148
|
+
}),
|
|
149
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
150
|
+
color: "error",
|
|
151
|
+
variant: "contained",
|
|
152
|
+
onClick: ()=>{
|
|
153
|
+
const { enrollment, action } = confirming;
|
|
154
|
+
setConfirming(null);
|
|
155
|
+
void act(enrollment, action, detail.trim() || undefined);
|
|
156
|
+
},
|
|
157
|
+
children: (_CONFIRM_confirming_action2 = CONFIRM[confirming.action]) == null ? void 0 : _CONFIRM_confirming_action2.label
|
|
158
|
+
})
|
|
159
|
+
]
|
|
160
|
+
})
|
|
161
|
+
]
|
|
162
|
+
}) : null
|
|
163
|
+
})
|
|
164
|
+
]
|
|
165
|
+
});
|
|
166
|
+
return {
|
|
167
|
+
busy,
|
|
168
|
+
choose,
|
|
169
|
+
menuItems,
|
|
170
|
+
dialogs
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
//# sourceMappingURL=enrollment-actions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/enrollment-actions.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 type { RowActionsMenuItem } from '@aglyn/shared-ui-jsx/components/row-actions-menu.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Button,\n Dialog,\n DialogActions,\n DialogContent,\n DialogContentText,\n DialogTitle,\n Stack,\n TextField,\n} from '@mui/material'\nimport { type ReactNode, useState } from 'react'\nimport type { OutreachEnrollmentAction } from '../model/outreach-api'\nimport type { OutreachEnrollment, OutreachSequenceStep } from '../model/outreach.types'\nimport { OutreachCurateStepDialog, outreachNextEmailStepIndex } from './curate-step-dialog'\nimport type { OutreachApi } from './use-outreach-api'\n\n/**\n * WHAT A MEMBER CAN DO TO ONE ENROLLMENT (AGL-2980, AGL-3324), in one place\n * for the two places it is offered: a row's menu on the Enrollments tab, and\n * the header of the person's own page (AGL-3332). The same actions, the same\n * words, the same two confirmations — a Stop that asked first in one place\n * and not in the other would be two products.\n */\n\n/** The four the action route takes, and curating the next step (AGL-3324). */\nexport type OutreachEnrollmentRowAction = OutreachEnrollmentAction | 'curate'\n\n/** What a member may do to an enrollment in the status it is in. */\nexport function outreachEnrollmentActionsFor(\n enrollment: OutreachEnrollment,\n steps: readonly OutreachSequenceStep[],\n): OutreachEnrollmentRowAction[] {\n const actions: OutreachEnrollmentRowAction[] = []\n // Rewrite the next email for this one person, while one is still to go.\n if (outreachNextEmailStepIndex(enrollment, steps) !== null) actions.push('curate')\n if (enrollment.status === 'active') actions.push('pause', 'stop')\n if (enrollment.status === 'paused') actions.push('resume', 'stop')\n if (enrollment.status !== 'opted_out') actions.push('do_not_contact')\n return actions\n}\n\nexport const OUTREACH_ENROLLMENT_ACTION_LABELS: Record<OutreachEnrollmentRowAction, string> = {\n curate: 'Curate next step',\n pause: 'Pause',\n resume: 'Resume',\n stop: 'Stop',\n do_not_contact: 'Mark do-not-contact',\n}\n\n/** The two actions that end something for good. */\nexport const OUTREACH_DESTRUCTIVE_ENROLLMENT_ACTIONS: readonly OutreachEnrollmentRowAction[] = [\n 'stop',\n 'do_not_contact',\n]\n\n/** What the snackbar says once an action lands. */\nconst DONE_LABELS: Record<Exclude<OutreachEnrollmentAction, 'do_not_contact'>, string> = {\n pause: 'Paused.',\n resume: 'Resumed.',\n stop: 'Stopped.',\n}\n\n/** The two actions that end something for good ask first. */\nconst CONFIRM: Partial<Record<OutreachEnrollmentAction, { title: string; body: string; label: string }>> = {\n stop: {\n title: 'Stop this enrollment?',\n body: 'They won’t get any more steps from this sequence, and they can’t be enrolled in it again.',\n label: 'Stop',\n },\n do_not_contact: {\n title: 'Mark do-not-contact?',\n body:\n 'The address goes on your organization’s do-not-contact list, which every sequence checks before ' +\n 'every send, and they are stopped in every sequence they are in.',\n label: 'Mark do-not-contact',\n },\n}\n\nexport interface OutreachEnrollmentActions {\n /** The enrollment an action is running on, if any. */\n busy: string | null\n /** Runs one action: straight away, after its confirmation, or in the curate dialog. */\n choose(enrollment: OutreachEnrollment, action: OutreachEnrollmentRowAction): void\n /** The actions as menu items, the destructive ones marked. */\n menuItems(\n enrollment: OutreachEnrollment,\n only?: (action: OutreachEnrollmentRowAction) => boolean,\n ): RowActionsMenuItem[]\n /** The confirmation and the curate dialog, rendered once wherever the actions are offered. */\n dialogs: ReactNode\n}\n\nexport function useOutreachEnrollmentActions(input: {\n api: OutreachApi\n steps: readonly OutreachSequenceStep[]\n}): OutreachEnrollmentActions {\n const { api, steps } = input\n const { enqueueSnackbar } = useSnackbar()\n const [confirming, setConfirming] = useState<{\n enrollment: OutreachEnrollment\n action: OutreachEnrollmentAction\n } | null>(null)\n const [detail, setDetail] = useState('')\n const [busy, setBusy] = useState<string | null>(null)\n const [curating, setCurating] = useState<OutreachEnrollment | null>(null)\n\n const act = async (enrollment: OutreachEnrollment, action: OutreachEnrollmentAction, why?: string) => {\n setBusy(enrollment.id)\n try {\n const answer = await api.actOnEnrollment(enrollment.id, action, why)\n const others = answer.stoppedOthers\n enqueueSnackbar(\n action === 'do_not_contact'\n ? `Marked do-not-contact${others ? `, and stopped in ${others} other ${others === 1 ? 'sequence' : 'sequences'}` : ''}.`\n : answer.changed\n ? DONE_LABELS[action]\n : 'Nothing to change.',\n { variant: 'success' },\n )\n } catch (error) {\n enqueueSnackbar((error as Error).message, { variant: 'error', allowDuplicate: true })\n } finally {\n setBusy(null)\n }\n }\n\n const choose = (enrollment: OutreachEnrollment, action: OutreachEnrollmentRowAction) => {\n if (action === 'curate') {\n setCurating(enrollment)\n } else if (CONFIRM[action]) {\n setDetail('')\n setConfirming({ enrollment, action })\n } else {\n void act(enrollment, action)\n }\n }\n\n const menuItems = (\n enrollment: OutreachEnrollment,\n only?: (action: OutreachEnrollmentRowAction) => boolean,\n ): RowActionsMenuItem[] =>\n outreachEnrollmentActionsFor(enrollment, steps)\n .filter((action) => !only || only(action))\n .map((action) => ({\n key: action,\n label: OUTREACH_ENROLLMENT_ACTION_LABELS[action],\n onClick: () => choose(enrollment, action),\n destructive: OUTREACH_DESTRUCTIVE_ENROLLMENT_ACTIONS.includes(action),\n disabled: busy === enrollment.id,\n }))\n\n const dialogs = (\n <>\n <OutreachCurateStepDialog enrollment={curating} steps={steps} api={api} onClose={() => setCurating(null)} />\n <Dialog open={Boolean(confirming)} onClose={() => setConfirming(null)} fullWidth maxWidth=\"xs\">\n {confirming ? (\n <>\n <DialogTitle>{CONFIRM[confirming.action]?.title}</DialogTitle>\n <DialogContent>\n <Stack spacing={2}>\n <DialogContentText>{CONFIRM[confirming.action]?.body}</DialogContentText>\n <TextField\n label=\"Why (optional)\"\n value={detail}\n onChange={(event) => setDetail(event.target.value)}\n fullWidth\n slotProps={{ htmlInput: { maxLength: 200 } }}\n />\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setConfirming(null)}>Cancel</Button>\n <Button\n color=\"error\"\n variant=\"contained\"\n onClick={() => {\n const { enrollment, action } = confirming\n setConfirming(null)\n void act(enrollment, action, detail.trim() || undefined)\n }}\n >\n {CONFIRM[confirming.action]?.label}\n </Button>\n </DialogActions>\n </>\n ) : null}\n </Dialog>\n </>\n )\n\n return { busy, choose, menuItems, dialogs }\n}\n"],"names":["useSnackbar","Button","Dialog","DialogActions","DialogContent","DialogContentText","DialogTitle","Stack","TextField","useState","OutreachCurateStepDialog","outreachNextEmailStepIndex","outreachEnrollmentActionsFor","enrollment","steps","actions","push","status","OUTREACH_ENROLLMENT_ACTION_LABELS","curate","pause","resume","stop","do_not_contact","OUTREACH_DESTRUCTIVE_ENROLLMENT_ACTIONS","DONE_LABELS","CONFIRM","title","body","label","useOutreachEnrollmentActions","input","api","enqueueSnackbar","confirming","setConfirming","detail","setDetail","busy","setBusy","curating","setCurating","act","action","why","id","answer","actOnEnrollment","others","stoppedOthers","changed","variant","error","message","allowDuplicate","choose","menuItems","only","filter","map","key","onClick","destructive","includes","disabled","dialogs","onClose","open","Boolean","fullWidth","maxWidth","spacing","value","onChange","event","target","slotProps","htmlInput","maxLength","color","trim","undefined"],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type ConsolePluginOrgMount } from '@aglyn/aglyn';
|
|
2
|
+
import type { OutreachMailboxesResult } from './use-outreach-mailboxes';
|
|
3
|
+
export interface OutreachEnrollmentDetailProps {
|
|
4
|
+
orgId: string;
|
|
5
|
+
/** The org the hub is mounted under: its slug and sites, for the CRM record's address. */
|
|
6
|
+
orgMount?: ConsolePluginOrgMount;
|
|
7
|
+
/** `/[orgSlug]/outreach/sequences`. */
|
|
8
|
+
sectionPath: string;
|
|
9
|
+
sequenceId: string;
|
|
10
|
+
enrollmentId: string;
|
|
11
|
+
mailboxes: OutreachMailboxesResult;
|
|
12
|
+
}
|
|
13
|
+
/** Where the CRM record the person is lives — the site's own CRM when the site is known. */
|
|
14
|
+
export declare function outreachCrmRecordHref(kind: 'lead' | 'contact', id: string | null | undefined, hostId: string, orgMount: Pick<ConsolePluginOrgMount, 'orgSlug' | 'hosts'> | undefined): string | null;
|
|
15
|
+
/** A Gmail thread, opened in the mailbox's own account. */
|
|
16
|
+
export declare function outreachGmailThreadUrl(threadId: string, mailboxEmail: string | null | undefined): string;
|
|
17
|
+
export declare function OutreachEnrollmentDetail(props: OutreachEnrollmentDetailProps): import("react").JSX.Element;
|
|
18
|
+
export declare namespace OutreachEnrollmentDetail {
|
|
19
|
+
var displayName: string;
|
|
20
|
+
}
|
|
21
|
+
export default OutreachEnrollmentDetail;
|