@aglyn/plugins-workflows 1.0.0-beta.143
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +52 -0
- package/package.json +53 -0
- package/src/index.d.ts +18 -0
- package/src/index.js +19 -0
- package/src/index.js.map +1 -0
- package/src/lib/components/automation-step-fields.component.d.ts +74 -0
- package/src/lib/components/automation-step-fields.component.js +1210 -0
- package/src/lib/components/automation-step-fields.component.js.map +1 -0
- package/src/lib/components/host-actions-card.component.d.ts +15 -0
- package/src/lib/components/host-actions-card.component.js +1229 -0
- package/src/lib/components/host-actions-card.component.js.map +1 -0
- package/src/lib/components/host-activity-card.component.d.ts +84 -0
- package/src/lib/components/host-activity-card.component.js +280 -0
- package/src/lib/components/host-activity-card.component.js.map +1 -0
- package/src/lib/components/host-run-history-card.component.d.ts +40 -0
- package/src/lib/components/host-run-history-card.component.js +284 -0
- package/src/lib/components/host-run-history-card.component.js.map +1 -0
- package/src/lib/components/host-webhooks-card.component.d.ts +15 -0
- package/src/lib/components/host-webhooks-card.component.js +421 -0
- package/src/lib/components/host-webhooks-card.component.js.map +1 -0
- package/src/lib/components/host-workflows-card.component.d.ts +18 -0
- package/src/lib/components/host-workflows-card.component.js +952 -0
- package/src/lib/components/host-workflows-card.component.js.map +1 -0
- package/src/lib/components/run-quota-line.component.d.ts +27 -0
- package/src/lib/components/run-quota-line.component.js +72 -0
- package/src/lib/components/run-quota-line.component.js.map +1 -0
- package/src/lib/components/use-automation-step-pickers.d.ts +27 -0
- package/src/lib/components/use-automation-step-pickers.js +205 -0
- package/src/lib/components/use-automation-step-pickers.js.map +1 -0
- package/src/lib/components/workflow-zones.d.ts +36 -0
- package/src/lib/components/workflow-zones.js +19 -0
- package/src/lib/components/workflow-zones.js.map +1 -0
- package/src/lib/components/workflows-console-page.d.ts +25 -0
- package/src/lib/components/workflows-console-page.js +112 -0
- package/src/lib/components/workflows-console-page.js.map +1 -0
- package/src/lib/components/workflows-console-sections.d.ts +34 -0
- package/src/lib/components/workflows-console-sections.js +50 -0
- package/src/lib/components/workflows-console-sections.js.map +1 -0
- package/src/lib/constants/bundle-common.d.ts +8 -0
- package/src/lib/constants/bundle-common.js +9 -0
- package/src/lib/constants/bundle-common.js.map +1 -0
- package/src/lib/declarations.server.d.ts +41 -0
- package/src/lib/declarations.server.js +54 -0
- package/src/lib/declarations.server.js.map +1 -0
- package/src/lib/engine/crm-action-steps.d.ts +129 -0
- package/src/lib/engine/crm-action-steps.js +432 -0
- package/src/lib/engine/crm-action-steps.js.map +1 -0
- package/src/lib/engine/flow-enrollments.d.ts +264 -0
- package/src/lib/engine/flow-enrollments.js +386 -0
- package/src/lib/engine/flow-enrollments.js.map +1 -0
- package/src/lib/engine/run-event-actions.d.ts +232 -0
- package/src/lib/engine/run-event-actions.js +1392 -0
- package/src/lib/engine/run-event-actions.js.map +1 -0
- package/src/lib/engine/run-event-automations.d.ts +28 -0
- package/src/lib/engine/run-event-automations.js +37 -0
- package/src/lib/engine/run-event-automations.js.map +1 -0
- package/src/lib/engine/run-event-workflows.d.ts +75 -0
- package/src/lib/engine/run-event-workflows.js +311 -0
- package/src/lib/engine/run-event-workflows.js.map +1 -0
- package/src/lib/engine/workflow-steps.d.ts +107 -0
- package/src/lib/engine/workflow-steps.js +124 -0
- package/src/lib/engine/workflow-steps.js.map +1 -0
- package/src/lib/plugin.d.ts +33 -0
- package/src/lib/plugin.js +94 -0
- package/src/lib/plugin.js.map +1 -0
- package/src/lib/server-automation-drafts.d.ts +90 -0
- package/src/lib/server-automation-drafts.js +429 -0
- package/src/lib/server-automation-drafts.js.map +1 -0
- package/src/lib/server-console.d.ts +27 -0
- package/src/lib/server-console.js +35 -0
- package/src/lib/server-console.js.map +1 -0
- package/src/lib/server.d.ts +29 -0
- package/src/lib/server.js +298 -0
- package/src/lib/server.js.map +1 -0
|
@@ -0,0 +1,1229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ 'use client';
|
|
17
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
18
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
19
|
+
import { ACTION_MAX_CONDITIONS, ACTION_MAX_STEPS, checkEntitlement, createResourceUid, CRM_ACTION_RECIPES, ELEMENT_SCOPED_SITE_EVENTS, HOST_ACTION_STEP_LABELS, HOST_EVENT_TYPES, hostActionDocument, hostActionRecipeId, hostEventLabel, hostEventPayloadHint, isSiteEventType, normalizeTriggerConditions, pluginDocsHelp, SITE_EVENT_TYPES, validateHostAction } from "@aglyn/aglyn";
|
|
20
|
+
import { automationPlaceholders, describeAutomationPlaceholder } from "@aglyn/aglyn/app-utils/automation-placeholders";
|
|
21
|
+
import { useConsoleWidgetSlot } from "@aglyn/aglyn/app-utils/console-widget-slot-context";
|
|
22
|
+
import { CardDisplay, useConfirmationContext } from "@aglyn/shared-ui-jsx";
|
|
23
|
+
import { ListPagination } from "@aglyn/shared-ui-jsx/components/list-pagination.component";
|
|
24
|
+
import { TABLE_PAGE_SIZE_DEFAULT } from "@aglyn/shared-ui-jsx/const/table-pagination";
|
|
25
|
+
import { useSnackbar } from "@aglyn/shared-ui-snackstack";
|
|
26
|
+
import { Timestamp } from "@aglyn/shared-util-timestamp";
|
|
27
|
+
import { Alert, Button, Dialog, DialogActions, DialogContent, DialogTitle, IconButton, ListItemText, Menu, MenuItem, Stack, Switch, TextField, Typography } from "@mui/material";
|
|
28
|
+
import { collection, doc, setDoc, updateDoc } from "firebase/firestore";
|
|
29
|
+
import { useCallback, useMemo, useRef, useState } from "react";
|
|
30
|
+
import { ceilingedWindow, collectionCeiling, useFirestore, useFirestoreCollection, useHostResourceApi, writeGuardedBySeed } from "@aglyn/tenant-feature-instance";
|
|
31
|
+
import { AutomationStepFields, defaultStep, placeholderState } from "./automation-step-fields.component.js";
|
|
32
|
+
import HostRunHistoryCard from "./host-run-history-card.component.js";
|
|
33
|
+
import { EDITOR_OPTION_CEILING, useAutomationStepPickers } from "./use-automation-step-pickers.js";
|
|
34
|
+
const CUSTOM_EVENT_VALUE = '__custom__';
|
|
35
|
+
/**
|
|
36
|
+
* How many action documents the card reads.
|
|
37
|
+
*
|
|
38
|
+
* A CEILING, not a page size — see the query, which explains why this one
|
|
39
|
+
* collection cannot be sliced by the server without making the count beneath
|
|
40
|
+
* the list a per-page number.
|
|
41
|
+
*
|
|
42
|
+
* `ACTIONS_MAX_PER_HOST` is 500, so the ceiling really can bite and the probe
|
|
43
|
+
* beside it is what says when it has.
|
|
44
|
+
*/ const ACTION_CEILING = 100;
|
|
45
|
+
const EMPTY_CONDITION_ROW = {
|
|
46
|
+
op: '',
|
|
47
|
+
field: '',
|
|
48
|
+
value: ''
|
|
49
|
+
};
|
|
50
|
+
/** Every Actions step, as the builder's "Do" picker offers it. */ const ACTION_STEP_KINDS = Object.entries(HOST_ACTION_STEP_LABELS).map(([value, label])=>({
|
|
51
|
+
value,
|
|
52
|
+
label
|
|
53
|
+
}));
|
|
54
|
+
/**
|
|
55
|
+
* A stored action — or one a recipe just built — as the editor holds it.
|
|
56
|
+
*
|
|
57
|
+
* One conversion for both doors, because they must agree: an action that
|
|
58
|
+
* round-trips through Edit and one a recipe hands over are the same shape
|
|
59
|
+
* to the editor, and a field the Edit path hydrated that the recipe path
|
|
60
|
+
* forgot would be a recipe whose conditions vanished on open. Site events
|
|
61
|
+
* are first-class (AGL-256/266): their selector/threshold/path config is
|
|
62
|
+
* kept through an edit. Structured conditions (AGL-557; chained AGL-565)
|
|
63
|
+
* become rows; a legacy single-condition doc normalizes to one row.
|
|
64
|
+
*/ function draftFromAction(action, id) {
|
|
65
|
+
var _ref, _action_name, _ref1, _ref2, _ref3, _action_steps, _ref4;
|
|
66
|
+
var _action_trigger, _action_trigger1, _action_trigger2, _action_trigger3, _action_trigger4, _action_trigger5, _action_trigger6, _action_trigger7, _action_trigger8, _action_trigger9, _action_trigger10, _action_trigger11, _action_trigger12;
|
|
67
|
+
const builtIn = HOST_EVENT_TYPES.includes((_action_trigger = action.trigger) == null ? void 0 : _action_trigger.event) || isSiteEventType(String((_ref = (_action_trigger1 = action.trigger) == null ? void 0 : _action_trigger1.event) != null ? _ref : ''));
|
|
68
|
+
const rows = normalizeTriggerConditions(action.trigger).map((condition)=>{
|
|
69
|
+
var _condition_op, _condition_field, _condition_value;
|
|
70
|
+
return {
|
|
71
|
+
op: (_condition_op = condition.op) != null ? _condition_op : '',
|
|
72
|
+
field: (_condition_field = condition.field) != null ? _condition_field : '',
|
|
73
|
+
value: (_condition_value = condition.value) != null ? _condition_value : ''
|
|
74
|
+
};
|
|
75
|
+
});
|
|
76
|
+
return {
|
|
77
|
+
id,
|
|
78
|
+
name: (_action_name = action.name) != null ? _action_name : '',
|
|
79
|
+
trigger: _extends({
|
|
80
|
+
event: builtIn ? action.trigger.event : CUSTOM_EVENT_VALUE,
|
|
81
|
+
filter: (_ref1 = (_action_trigger2 = action.trigger) == null ? void 0 : _action_trigger2.filter) != null ? _ref1 : '',
|
|
82
|
+
selector: (_ref2 = (_action_trigger3 = action.trigger) == null ? void 0 : _action_trigger3.selector) != null ? _ref2 : '',
|
|
83
|
+
threshold: (_action_trigger4 = action.trigger) == null ? void 0 : _action_trigger4.threshold,
|
|
84
|
+
pathPattern: (_ref3 = (_action_trigger5 = action.trigger) == null ? void 0 : _action_trigger5.pathPattern) != null ? _ref3 : '',
|
|
85
|
+
oncePerVisitor: ((_action_trigger6 = action.trigger) == null ? void 0 : _action_trigger6.oncePerVisitor) === true,
|
|
86
|
+
oncePerSession: ((_action_trigger7 = action.trigger) == null ? void 0 : _action_trigger7.oncePerSession) === true,
|
|
87
|
+
everyTime: ((_action_trigger8 = action.trigger) == null ? void 0 : _action_trigger8.everyTime) === true
|
|
88
|
+
}, Number((_action_trigger9 = action.trigger) == null ? void 0 : _action_trigger9.cooldownMinutes) >= 1 ? {
|
|
89
|
+
cooldownMinutes: Number((_action_trigger10 = action.trigger) == null ? void 0 : _action_trigger10.cooldownMinutes)
|
|
90
|
+
} : {}),
|
|
91
|
+
steps: (_action_steps = action.steps) != null ? _action_steps : [],
|
|
92
|
+
enabled: action.enabled !== false,
|
|
93
|
+
customEvent: builtIn ? '' : (_ref4 = (_action_trigger11 = action.trigger) == null ? void 0 : _action_trigger11.event) != null ? _ref4 : '',
|
|
94
|
+
conditionRows: rows.length ? rows : [
|
|
95
|
+
EMPTY_CONDITION_ROW
|
|
96
|
+
],
|
|
97
|
+
conditionCombinator: ((_action_trigger12 = action.trigger) == null ? void 0 : _action_trigger12.combinator) === 'or' ? 'or' : 'and',
|
|
98
|
+
// A recipe stamps what it builds, and a stored action carries what it
|
|
99
|
+
// was saved with — one reader for both doors, unknown kept unknown.
|
|
100
|
+
recipe: hostActionRecipeId(action)
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/** A trigger bound to one element, which makes the row an interaction. */ const LEAF_SELECTOR = /^\[data-aglyn="leaf:.+"\]$/;
|
|
104
|
+
/**
|
|
105
|
+
* Actions builder (AGL-148): HubSpot-style enrollment — trigger event →
|
|
106
|
+
* optional filter → ordered steps (run workflow, site alert, custom
|
|
107
|
+
* event, dataset append). Runs server-side via runEventActions; paid
|
|
108
|
+
* feature (`actions` flag, Pro+, `actionRunsPerMonth` metered).
|
|
109
|
+
*/ export function HostActionsCard(props) {
|
|
110
|
+
var _ref, _ref1, _ref2, _ref3, _ref4, _ref5, _hostEventPayloadHint, _ref6, _ref7, _ref8, _ref9, _ref10, _ref11, _ref12, _ref13, _ref14, _ref15, _ref16, _ref17, _ref18, _ref19, _ref20, _ref21, _runsFor_name;
|
|
111
|
+
var _CRM_ACTION_RECIPES_find;
|
|
112
|
+
const { hostId, org } = props;
|
|
113
|
+
// Org-shared data root (AGL-237). Null until the org lookup settles
|
|
114
|
+
// (AGL-1061), and for a host with no owning org — the pre-migration host
|
|
115
|
+
// path is gone (AGL-1050), so the dataset and list pickers stay empty
|
|
116
|
+
// instead of offering rows from a path nothing else reads.
|
|
117
|
+
const firestore = useFirestore();
|
|
118
|
+
// Action creation is server-owned since AGL-2266 (the cap); every other
|
|
119
|
+
// action write on this surface stays client-direct.
|
|
120
|
+
const createResource = useHostResourceApi();
|
|
121
|
+
const { enqueueSnackbar } = useSnackbar();
|
|
122
|
+
const { confirm } = useConfirmationContext();
|
|
123
|
+
/**
|
|
124
|
+
* The shell's zone renderer, for what other plugins add to automations
|
|
125
|
+
* (AGL-2919); `null` outside the console shell, where there is no workspace
|
|
126
|
+
* to gate on.
|
|
127
|
+
*/ const ExtensionZone = useConsoleWidgetSlot();
|
|
128
|
+
const { data: actionDocs, status: actionsStatus, /**
|
|
129
|
+
* The action rows this card's editor is seeded from are unconfirmed by
|
|
130
|
+
* the server (AGL-1358). Editing an action copies the whole stored row
|
|
131
|
+
* into `draft` — name, trigger and every step — and writes all of it
|
|
132
|
+
* back, so `merge: true` protects nothing and a cached seed reverts a
|
|
133
|
+
* colleague's newer version of the steps nobody here touched.
|
|
134
|
+
*
|
|
135
|
+
* The same shape the interaction builder guards (AGL-1066); this is its
|
|
136
|
+
* card twin, and it was the one that never got the guard.
|
|
137
|
+
*/ fromCache: actionsFromCache } = useFirestoreCollection(/*
|
|
138
|
+
* ORDERED AND CEILINGED, deliberately not paged by the query (AGL-2501) —
|
|
139
|
+
* the same decision as the workflows card beside it.
|
|
140
|
+
*
|
|
141
|
+
* `limit(100)` alone is answered in DOCUMENT-ID order, so the window was a
|
|
142
|
+
* pseudo-random hundred that the `localeCompare` below arranged
|
|
143
|
+
* alphabetically, and nothing said the list was bounded.
|
|
144
|
+
*
|
|
145
|
+
* `collectionCeiling` does not change WHICH hundred — document-id order is
|
|
146
|
+
* what the bare cap already returned. What it changes is that the order is
|
|
147
|
+
* NAMED, so the obvious next edit is caught: ordering on `name` would HIDE
|
|
148
|
+
* every action written without one rather than mis-sorting the list, and
|
|
149
|
+
* `/api/hosts/resources` validates no field for presence while
|
|
150
|
+
* `IMPORTABLE_FIELDS.actions` copies one only if the export carried it.
|
|
151
|
+
*
|
|
152
|
+
* The QUERY is not paged because this collection holds TWO audiences. A
|
|
153
|
+
* row whose trigger names a leaf selector is an element interaction, which
|
|
154
|
+
* belongs to its document and is reported below as a count rather than
|
|
155
|
+
* listed; the rest are the site's actions. Both partitions are derived
|
|
156
|
+
* from the rows in hand, so a server page would make "3 interactions are
|
|
157
|
+
* set up on their own elements" mean "3 on this page" — a count that is a
|
|
158
|
+
* window length, which is the defect this sweep keeps finding rather than
|
|
159
|
+
* a shape it should add.
|
|
160
|
+
*/ ()=>collectionCeiling(collection(firestore, 'hosts', hostId, 'actions'), ACTION_CEILING), [
|
|
161
|
+
firestore,
|
|
162
|
+
hostId
|
|
163
|
+
], {
|
|
164
|
+
idField: '$id'
|
|
165
|
+
});
|
|
166
|
+
const { rows: readActions, truncated: actionsTruncated } = ceilingedWindow(actionDocs, ACTION_CEILING);
|
|
167
|
+
const [draft, setDraft] = useState(null);
|
|
168
|
+
/**
|
|
169
|
+
* The editor has been opened at least once in this session.
|
|
170
|
+
*
|
|
171
|
+
* A LATCH rather than `draft` itself, because the pickers below key their
|
|
172
|
+
* listeners on it. Tracking the dialog would tear those subscriptions down
|
|
173
|
+
* on Cancel and pay for them again on the next Edit, so a merchant working
|
|
174
|
+
* through ten actions would buy the same six windows ten times — worse than
|
|
175
|
+
* the mount-time read this replaces. Latched, a reader who never edits pays
|
|
176
|
+
* nothing and a reader who edits pays once.
|
|
177
|
+
*/ const [editorOpened, setEditorOpened] = useState(false);
|
|
178
|
+
if (draft && !editorOpened) setEditorOpened(true);
|
|
179
|
+
/*
|
|
180
|
+
* The step editor's six option lists, read on the same latch as the
|
|
181
|
+
* Workflows builder's — one hook, because the two editors offer one set of
|
|
182
|
+
* steps and a second copy would be a second read cost to keep in agreement.
|
|
183
|
+
*/ const { pickers: stepPickers, truncated: truncatedPickers } = useAutomationStepPickers(hostId, editorOpened);
|
|
184
|
+
const liveActions = readActions.filter((action)=>!action.deletedAt);
|
|
185
|
+
/*
|
|
186
|
+
* Opens a listed action in the editor, for a widget in the `hostAutomations`
|
|
187
|
+
* zone — one that drafted an action and offers to open it. Read through a
|
|
188
|
+
* ref so the callback keeps one identity while the list changes beneath it,
|
|
189
|
+
* and answers `false` for an id the list has not read yet.
|
|
190
|
+
*/ const listedRef = useRef(liveActions);
|
|
191
|
+
listedRef.current = liveActions;
|
|
192
|
+
const openAction = useCallback((actionId)=>{
|
|
193
|
+
const action = listedRef.current.find((row)=>row.$id === actionId);
|
|
194
|
+
if (!action) return false;
|
|
195
|
+
setDraft(draftFromAction(action, action.$id));
|
|
196
|
+
return true;
|
|
197
|
+
}, []);
|
|
198
|
+
/**
|
|
199
|
+
* Element interactions are not listed here.
|
|
200
|
+
*
|
|
201
|
+
* An ACTION is something the SITE does — an order was placed, a form was
|
|
202
|
+
* submitted. An INTERACTION is something an ELEMENT does, and it belongs to
|
|
203
|
+
* the document that holds the element: it publishes and rolls back with it,
|
|
204
|
+
* travels with it, and is deleted with it. This page is the site's
|
|
205
|
+
* automation list, and a nav menu's hover timing sitting in it beside "when
|
|
206
|
+
* an order is placed" was the tell that the two had been conflated.
|
|
207
|
+
*
|
|
208
|
+
* These rows are the ones written BEFORE the move, still bound by selector.
|
|
209
|
+
* They keep running and stay editable — on their element, in the besigner,
|
|
210
|
+
* which is now the only place they appear. Counted rather than hidden
|
|
211
|
+
* outright, so the page can say where they went instead of appearing to
|
|
212
|
+
* have lost them.
|
|
213
|
+
*/ const elementScoped = liveActions.filter((action)=>{
|
|
214
|
+
var _ref;
|
|
215
|
+
var _action_trigger;
|
|
216
|
+
return LEAF_SELECTOR.test(String((_ref = action == null ? void 0 : (_action_trigger = action.trigger) == null ? void 0 : _action_trigger.selector) != null ? _ref : ''));
|
|
217
|
+
});
|
|
218
|
+
// Sorting is safe here in a way it is not on a paged list: these rows are
|
|
219
|
+
// the whole collection below the ceiling, not a slice of one.
|
|
220
|
+
const actions = liveActions.filter((action)=>!elementScoped.includes(action)).sort((a, b)=>{
|
|
221
|
+
var _a_name, _b_name;
|
|
222
|
+
return String((_a_name = a.name) != null ? _a_name : '').localeCompare(String((_b_name = b.name) != null ? _b_name : ''));
|
|
223
|
+
});
|
|
224
|
+
// The page is a SLICE: the rows are already in hand, and both the element
|
|
225
|
+
// interaction count and the alphabetical order need all of them.
|
|
226
|
+
const [page, setPage] = useState(0);
|
|
227
|
+
const [pageSize, setPageSize] = useState(TABLE_PAGE_SIZE_DEFAULT);
|
|
228
|
+
const visibleActions = useMemo(()=>actions.slice(page * pageSize, page * pageSize + pageSize), [
|
|
229
|
+
actions,
|
|
230
|
+
page,
|
|
231
|
+
pageSize
|
|
232
|
+
]);
|
|
233
|
+
const patch = useCallback((updater)=>setDraft((previous)=>previous ? updater(previous) : previous), []);
|
|
234
|
+
/** The saved action the editor has open, as the `automationEditor` zone names it. */ const draftId = (_ref = draft == null ? void 0 : draft.id) != null ? _ref : null;
|
|
235
|
+
const draftName = (_ref1 = draft == null ? void 0 : draft.name) != null ? _ref1 : '';
|
|
236
|
+
const editorTarget = useMemo(()=>draftId ? {
|
|
237
|
+
type: 'action',
|
|
238
|
+
id: draftId,
|
|
239
|
+
name: draftName
|
|
240
|
+
} : null, // The name the editor opened with: the zone reads the automation as it is
|
|
241
|
+
// stored, so a rename being typed does not make it a different one.
|
|
242
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
243
|
+
[
|
|
244
|
+
draftId
|
|
245
|
+
]);
|
|
246
|
+
/**
|
|
247
|
+
* Whether this workspace may open the editor at all. One gate for the
|
|
248
|
+
* blank action and the recipes: a recipe is an action, so the plan that
|
|
249
|
+
* carries one carries the other, and the refusal reads the same.
|
|
250
|
+
*/ const actionsEntitled = useCallback(()=>{
|
|
251
|
+
if (checkEntitlement(org, 'actions')) return true;
|
|
252
|
+
enqueueSnackbar('The actions builder requires a Pro plan — see Billing to upgrade', {
|
|
253
|
+
variant: 'warning',
|
|
254
|
+
persist: false
|
|
255
|
+
});
|
|
256
|
+
return false;
|
|
257
|
+
}, [
|
|
258
|
+
org,
|
|
259
|
+
enqueueSnackbar
|
|
260
|
+
]);
|
|
261
|
+
const handleAdd = useCallback(()=>{
|
|
262
|
+
if (!actionsEntitled()) return;
|
|
263
|
+
setDraft({
|
|
264
|
+
id: null,
|
|
265
|
+
name: '',
|
|
266
|
+
trigger: {
|
|
267
|
+
event: 'formSubmission',
|
|
268
|
+
filter: ''
|
|
269
|
+
},
|
|
270
|
+
steps: [
|
|
271
|
+
defaultStep('siteAlert')
|
|
272
|
+
],
|
|
273
|
+
enabled: true,
|
|
274
|
+
customEvent: '',
|
|
275
|
+
conditionRows: [
|
|
276
|
+
EMPTY_CONDITION_ROW
|
|
277
|
+
],
|
|
278
|
+
conditionCombinator: 'and',
|
|
279
|
+
recipe: null
|
|
280
|
+
});
|
|
281
|
+
}, [
|
|
282
|
+
actionsEntitled
|
|
283
|
+
]);
|
|
284
|
+
/*
|
|
285
|
+
* THE RECIPES (AGL-2626).
|
|
286
|
+
*
|
|
287
|
+
* A recipe opens the editor prefilled and writes nothing: the draft it
|
|
288
|
+
* builds is the one Save would persist, and until then it is state in
|
|
289
|
+
* this card and nowhere else. Three recipes open at once; "Tag by form"
|
|
290
|
+
* needs a form first, and the form is this site's — `hosts/{hostId}/forms`
|
|
291
|
+
* — so the picker is the one host-scoped piece of the feature, held here
|
|
292
|
+
* rather than in the catalog.
|
|
293
|
+
*/ const [recipesAnchor, setRecipesAnchor] = useState(null);
|
|
294
|
+
const [formPickFor, setFormPickFor] = useState(null);
|
|
295
|
+
const [pickedFormId, setPickedFormId] = useState('');
|
|
296
|
+
/**
|
|
297
|
+
* The form picker has been opened at least once — the `editorOpened`
|
|
298
|
+
* latch's twin, and for the same reason: the forms window is read for
|
|
299
|
+
* this picker only, and a reader who never opens it pays nothing.
|
|
300
|
+
*/ const [formPickerOpened, setFormPickerOpened] = useState(false);
|
|
301
|
+
if (formPickFor && !formPickerOpened) setFormPickerOpened(true);
|
|
302
|
+
const { data: formRead } = useFirestoreCollection(()=>formPickerOpened ? collectionCeiling(collection(firestore, 'hosts', hostId, 'forms'), EDITOR_OPTION_CEILING) : null, [
|
|
303
|
+
firestore,
|
|
304
|
+
hostId,
|
|
305
|
+
formPickerOpened
|
|
306
|
+
], {
|
|
307
|
+
idField: '$id'
|
|
308
|
+
});
|
|
309
|
+
const { rows: formDocs, truncated: formsTruncated } = ceilingedWindow(formRead, EDITOR_OPTION_CEILING);
|
|
310
|
+
// An archived form collects nothing, so a recipe keyed on it would never
|
|
311
|
+
// fire; the name falls back to the id the way every form list's does.
|
|
312
|
+
const formOptions = (formDocs != null ? formDocs : []).filter((form)=>!form.archivedAt).map((form)=>{
|
|
313
|
+
var _form_displayName;
|
|
314
|
+
return {
|
|
315
|
+
id: form.$id,
|
|
316
|
+
name: String((_form_displayName = form.displayName) != null ? _form_displayName : '').trim() || form.$id
|
|
317
|
+
};
|
|
318
|
+
}).sort((a, b)=>a.name.localeCompare(b.name));
|
|
319
|
+
const handleRecipe = useCallback((recipe)=>{
|
|
320
|
+
setRecipesAnchor(null);
|
|
321
|
+
if (!actionsEntitled()) return;
|
|
322
|
+
if (recipe.needs === 'form') {
|
|
323
|
+
setPickedFormId('');
|
|
324
|
+
setFormPickFor(recipe);
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
setDraft(draftFromAction(recipe.build(), null));
|
|
328
|
+
}, [
|
|
329
|
+
actionsEntitled
|
|
330
|
+
]);
|
|
331
|
+
const handleFormPicked = useCallback(()=>{
|
|
332
|
+
const recipe = formPickFor;
|
|
333
|
+
const form = formOptions.find((option)=>option.id === pickedFormId);
|
|
334
|
+
if (!recipe || !form) return;
|
|
335
|
+
setFormPickFor(null);
|
|
336
|
+
setDraft(draftFromAction(recipe.build({
|
|
337
|
+
form
|
|
338
|
+
}), null));
|
|
339
|
+
}, [
|
|
340
|
+
formPickFor,
|
|
341
|
+
formOptions,
|
|
342
|
+
pickedFormId
|
|
343
|
+
]);
|
|
344
|
+
// Run log + test runs (AGL-266).
|
|
345
|
+
const [runsFor, setRunsFor] = useState(null);
|
|
346
|
+
const handleTestRun = useCallback(async (action)=>{
|
|
347
|
+
try {
|
|
348
|
+
var _action_trigger;
|
|
349
|
+
const response = await fetch('/api/events/dispatch', {
|
|
350
|
+
method: 'POST',
|
|
351
|
+
headers: {
|
|
352
|
+
'Content-Type': 'application/json'
|
|
353
|
+
},
|
|
354
|
+
body: JSON.stringify({
|
|
355
|
+
hostId,
|
|
356
|
+
actionId: action.$id,
|
|
357
|
+
event: (_action_trigger = action.trigger) == null ? void 0 : _action_trigger.event,
|
|
358
|
+
payload: {
|
|
359
|
+
path: '/console-test',
|
|
360
|
+
test: 'true'
|
|
361
|
+
}
|
|
362
|
+
})
|
|
363
|
+
});
|
|
364
|
+
const payload = await response.json().catch(()=>({}));
|
|
365
|
+
if (!response.ok) {
|
|
366
|
+
var _ref;
|
|
367
|
+
return void enqueueSnackbar((_ref = payload == null ? void 0 : payload.error) != null ? _ref : 'Test run failed', {
|
|
368
|
+
variant: 'warning',
|
|
369
|
+
persist: false
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
const alerts = Array.isArray(payload == null ? void 0 : payload.alerts) ? payload.alerts : [];
|
|
373
|
+
enqueueSnackbar(alerts.length ? `Test ran — first alert: ${alerts[0].message}` : 'Test ran — server steps executed (see Runs)', {
|
|
374
|
+
variant: 'success',
|
|
375
|
+
persist: false
|
|
376
|
+
});
|
|
377
|
+
} catch (error) {
|
|
378
|
+
console.error(error);
|
|
379
|
+
enqueueSnackbar('Test run failed', {
|
|
380
|
+
variant: 'error'
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
}, [
|
|
384
|
+
hostId,
|
|
385
|
+
enqueueSnackbar
|
|
386
|
+
]);
|
|
387
|
+
const handleSave = useCallback(async ()=>{
|
|
388
|
+
var _draft_trigger_filter, _draft_trigger_selector, _draft_trigger_pathPattern;
|
|
389
|
+
if (!draft) return;
|
|
390
|
+
const isCustom = draft.trigger.event === CUSTOM_EVENT_VALUE;
|
|
391
|
+
const candidate = _extends({
|
|
392
|
+
name: draft.name.trim().slice(0, 60),
|
|
393
|
+
trigger: _extends({
|
|
394
|
+
event: isCustom ? draft.customEvent.trim() : draft.trigger.event
|
|
395
|
+
}, ((_draft_trigger_filter = draft.trigger.filter) == null ? void 0 : _draft_trigger_filter.trim()) ? {
|
|
396
|
+
filter: draft.trigger.filter.trim()
|
|
397
|
+
} : {}, ((_draft_trigger_selector = draft.trigger.selector) == null ? void 0 : _draft_trigger_selector.trim()) ? {
|
|
398
|
+
selector: draft.trigger.selector.trim()
|
|
399
|
+
} : {}, Number(draft.trigger.threshold) > 0 ? {
|
|
400
|
+
threshold: Number(draft.trigger.threshold)
|
|
401
|
+
} : {}, ((_draft_trigger_pathPattern = draft.trigger.pathPattern) == null ? void 0 : _draft_trigger_pathPattern.trim()) ? {
|
|
402
|
+
pathPattern: draft.trigger.pathPattern.trim()
|
|
403
|
+
} : {}, draft.trigger.oncePerVisitor === true ? {
|
|
404
|
+
oncePerVisitor: true
|
|
405
|
+
} : {}, draft.trigger.oncePerSession === true ? {
|
|
406
|
+
oncePerSession: true
|
|
407
|
+
} : {}, Number(draft.trigger.cooldownMinutes) >= 1 ? {
|
|
408
|
+
cooldownMinutes: Number(draft.trigger.cooldownMinutes)
|
|
409
|
+
} : {}, draft.trigger.everyTime === true ? {
|
|
410
|
+
everyTime: true
|
|
411
|
+
} : {}, draft.conditionRows.some((row)=>row.op) ? {
|
|
412
|
+
conditions: draft.conditionRows.filter((row)=>row.op).map((row)=>_extends({
|
|
413
|
+
field: row.field.trim(),
|
|
414
|
+
op: row.op
|
|
415
|
+
}, row.op !== 'notEmpty' ? {
|
|
416
|
+
value: row.value.trim()
|
|
417
|
+
} : {})),
|
|
418
|
+
combinator: draft.conditionCombinator
|
|
419
|
+
} : {}),
|
|
420
|
+
steps: draft.steps,
|
|
421
|
+
enabled: draft.enabled !== false
|
|
422
|
+
}, draft.recipe !== undefined ? {
|
|
423
|
+
recipe: draft.recipe
|
|
424
|
+
} : {});
|
|
425
|
+
const problem = validateHostAction(candidate);
|
|
426
|
+
if (problem) {
|
|
427
|
+
return void enqueueSnackbar(problem, {
|
|
428
|
+
variant: 'warning',
|
|
429
|
+
persist: false
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
try {
|
|
433
|
+
var _draft_id;
|
|
434
|
+
const id = (_draft_id = draft.id) != null ? _draft_id : createResourceUid();
|
|
435
|
+
/**
|
|
436
|
+
* Refuse an EDIT whose seed the server never confirmed (AGL-1358).
|
|
437
|
+
*
|
|
438
|
+
* The payload is the whole action. `merge: true` is here so the
|
|
439
|
+
* frequency caps and conditions can be nulled explicitly, not to
|
|
440
|
+
* protect untouched fields — there are none, they are all in the
|
|
441
|
+
* payload. So a cached seed does not lose one edit, it reverts the
|
|
442
|
+
* trigger and every step to whatever the cache last held, on an
|
|
443
|
+
* automation that is running on a live site.
|
|
444
|
+
*
|
|
445
|
+
* Only the edit path. A NEW action is built from `EMPTY_*` defaults at
|
|
446
|
+
* a fresh uid and can overwrite nothing; the first snapshot of any
|
|
447
|
+
* listener is `fromCache: true`, so guarding a create would refuse a
|
|
448
|
+
* save that was never unsafe.
|
|
449
|
+
*
|
|
450
|
+
* The guard WRAPS the write — the interaction builder's early return
|
|
451
|
+
* is the shape you can keep while losing the protection.
|
|
452
|
+
*/ /**
|
|
453
|
+
* The action DOCUMENT is created by the server (AGL-2266).
|
|
454
|
+
*
|
|
455
|
+
* `hosts/{hostId}/actions` was in none of the host catch-all's exclusion
|
|
456
|
+
* lists, so any editor could create one client-direct on any plan and
|
|
457
|
+
* nothing counted them. /api/hosts/resources now owns the create and
|
|
458
|
+
* holds `ACTIONS_MAX_PER_HOST` from a server read.
|
|
459
|
+
*
|
|
460
|
+
* Only the create moves, and it writes a SHELL. The merge-set below is
|
|
461
|
+
* untouched and still carries the whole candidate — trigger, steps,
|
|
462
|
+
* conditions, the explicit nulls AGL-274/557 depend on — so no field can
|
|
463
|
+
* be lost to the route's allow-list, and for an existing action this
|
|
464
|
+
* path does not run at all.
|
|
465
|
+
*/ if (!draft.id) {
|
|
466
|
+
try {
|
|
467
|
+
var _ref, _candidate_name;
|
|
468
|
+
await createResource({
|
|
469
|
+
hostId,
|
|
470
|
+
resource: 'action',
|
|
471
|
+
id,
|
|
472
|
+
data: {
|
|
473
|
+
name: (_ref = (_candidate_name = candidate.name) != null ? _candidate_name : draft.name) != null ? _ref : 'Untitled action'
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
} catch (error) {
|
|
477
|
+
var _ref1;
|
|
478
|
+
return void enqueueSnackbar((_ref1 = error == null ? void 0 : error.message) != null ? _ref1 : 'Could not create the interaction', {
|
|
479
|
+
variant: 'error'
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
const verdict = await writeGuardedBySeed({
|
|
484
|
+
subject: 'action',
|
|
485
|
+
unreadable: Boolean(draft.id) && actionsStatus === 'error',
|
|
486
|
+
fromCache: Boolean(draft.id) && actionsFromCache
|
|
487
|
+
}, async ()=>{
|
|
488
|
+
await setDoc(doc(firestore, 'hosts', hostId, 'actions', id), _extends({}, hostActionDocument(candidate), {
|
|
489
|
+
updatedAt: Timestamp.now()
|
|
490
|
+
}, draft.id ? {} : {
|
|
491
|
+
createdAt: Timestamp.now()
|
|
492
|
+
}), {
|
|
493
|
+
merge: true
|
|
494
|
+
});
|
|
495
|
+
});
|
|
496
|
+
// A refusal keeps the editor open with everything that was typed.
|
|
497
|
+
if (!verdict.ok) {
|
|
498
|
+
return void enqueueSnackbar(verdict.message, {
|
|
499
|
+
variant: 'warning',
|
|
500
|
+
persist: false
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
setDraft(null);
|
|
504
|
+
enqueueSnackbar('Action saved', {
|
|
505
|
+
variant: 'success',
|
|
506
|
+
persist: false
|
|
507
|
+
});
|
|
508
|
+
} catch (error) {
|
|
509
|
+
console.error(error);
|
|
510
|
+
enqueueSnackbar('An error has occurred', {
|
|
511
|
+
variant: 'error',
|
|
512
|
+
allowDuplicate: true
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
}, [
|
|
516
|
+
draft,
|
|
517
|
+
firestore,
|
|
518
|
+
createResource,
|
|
519
|
+
hostId,
|
|
520
|
+
enqueueSnackbar,
|
|
521
|
+
actionsFromCache,
|
|
522
|
+
actionsStatus
|
|
523
|
+
]);
|
|
524
|
+
const handleDelete = useCallback((action)=>async ()=>{
|
|
525
|
+
const confirmed = await confirm({
|
|
526
|
+
title: 'Delete this action?',
|
|
527
|
+
description: `"${action.name}" stops running on its trigger.`,
|
|
528
|
+
confirmationText: 'Delete',
|
|
529
|
+
confirmationButtonProps: {
|
|
530
|
+
color: 'error'
|
|
531
|
+
}
|
|
532
|
+
}).then(()=>true).catch(()=>false);
|
|
533
|
+
if (!confirmed) return;
|
|
534
|
+
await updateDoc(doc(firestore, 'hosts', hostId, 'actions', action.$id), {
|
|
535
|
+
deletedAt: Timestamp.now()
|
|
536
|
+
});
|
|
537
|
+
}, [
|
|
538
|
+
confirm,
|
|
539
|
+
firestore,
|
|
540
|
+
hostId
|
|
541
|
+
]);
|
|
542
|
+
const handleToggle = useCallback((action)=>async (event)=>{
|
|
543
|
+
const enabled = event.target.checked;
|
|
544
|
+
/*
|
|
545
|
+
* A placeholder is a value nobody has supplied yet. Switched on, the
|
|
546
|
+
* step holding it fails every run and a condition holding one never
|
|
547
|
+
* matches, so turning such an automation on is confirmed, naming what
|
|
548
|
+
* is still missing.
|
|
549
|
+
*/ const missing = enabled ? automationPlaceholders(action) : [];
|
|
550
|
+
if (missing.length) {
|
|
551
|
+
const confirmed = await confirm({
|
|
552
|
+
title: 'Switch on with placeholders?',
|
|
553
|
+
description: `"${action.name}" still has ${missing.length === 1 ? 'a placeholder' : `${missing.length} placeholders`} ` + `to fill in: ${missing.slice(0, 3).map(describeAutomationPlaceholder).join('; ')}` + `${missing.length > 3 ? '; …' : ''}. Open it with Edit to fill ` + `${missing.length === 1 ? 'it' : 'them'} in first.`,
|
|
554
|
+
confirmationText: 'Switch on anyway'
|
|
555
|
+
}).then(()=>true).catch(()=>false);
|
|
556
|
+
if (!confirmed) return;
|
|
557
|
+
}
|
|
558
|
+
await updateDoc(doc(firestore, 'hosts', hostId, 'actions', action.$id), {
|
|
559
|
+
enabled
|
|
560
|
+
});
|
|
561
|
+
}, [
|
|
562
|
+
confirm,
|
|
563
|
+
firestore,
|
|
564
|
+
hostId
|
|
565
|
+
]);
|
|
566
|
+
return /*#__PURE__*/ _jsxs(CardDisplay, {
|
|
567
|
+
header: 'Actions',
|
|
568
|
+
help: pluginDocsHelp('actionsBuilder', {
|
|
569
|
+
anchor: '#create-an-action'
|
|
570
|
+
}),
|
|
571
|
+
contentGutterX: true,
|
|
572
|
+
contentGutterY: true,
|
|
573
|
+
children: [
|
|
574
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
575
|
+
spacing: 1,
|
|
576
|
+
children: [
|
|
577
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
578
|
+
variant: "body2",
|
|
579
|
+
color: "text.secondary",
|
|
580
|
+
children: 'When a site event fires, run automations in order — trigger a ' + 'workflow, show the visitor an alert, chain a custom event, or ' + 'write to a dataset. Pro plans and up.'
|
|
581
|
+
}),
|
|
582
|
+
visibleActions.map((action)=>{
|
|
583
|
+
var _action_steps, _ref;
|
|
584
|
+
var _action_trigger, _action_trigger1;
|
|
585
|
+
const missing = automationPlaceholders(action).length;
|
|
586
|
+
return /*#__PURE__*/ _jsxs(Stack, {
|
|
587
|
+
direction: "row",
|
|
588
|
+
spacing: 1,
|
|
589
|
+
sx: {
|
|
590
|
+
alignItems: 'center'
|
|
591
|
+
},
|
|
592
|
+
children: [
|
|
593
|
+
/*#__PURE__*/ _jsx(Switch, {
|
|
594
|
+
size: "small",
|
|
595
|
+
checked: action.enabled !== false,
|
|
596
|
+
onChange: handleToggle(action)
|
|
597
|
+
}),
|
|
598
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
599
|
+
sx: {
|
|
600
|
+
flex: 1,
|
|
601
|
+
minWidth: 0
|
|
602
|
+
},
|
|
603
|
+
children: [
|
|
604
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
605
|
+
variant: "body2",
|
|
606
|
+
noWrap: true,
|
|
607
|
+
children: action.name
|
|
608
|
+
}),
|
|
609
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
610
|
+
variant: "caption",
|
|
611
|
+
color: "text.secondary",
|
|
612
|
+
noWrap: true,
|
|
613
|
+
children: `on ${hostEventLabel((_action_trigger = action.trigger) == null ? void 0 : _action_trigger.event)}` + ` · ${((_action_steps = action.steps) != null ? _action_steps : []).map((step)=>{
|
|
614
|
+
var _HOST_ACTION_STEP_LABELS_step_type;
|
|
615
|
+
return (_HOST_ACTION_STEP_LABELS_step_type = HOST_ACTION_STEP_LABELS[step.type]) != null ? _HOST_ACTION_STEP_LABELS_step_type : step.type;
|
|
616
|
+
}).join(' → ')}`
|
|
617
|
+
}),
|
|
618
|
+
missing ? /*#__PURE__*/ _jsx(Typography, {
|
|
619
|
+
variant: "caption",
|
|
620
|
+
color: "warning.main",
|
|
621
|
+
noWrap: true,
|
|
622
|
+
children: missing === 1 ? '1 placeholder to fill in' : `${missing} placeholders to fill in`
|
|
623
|
+
}) : null
|
|
624
|
+
]
|
|
625
|
+
}),
|
|
626
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
627
|
+
size: "small",
|
|
628
|
+
onClick: ()=>setDraft(draftFromAction(action, action.$id)),
|
|
629
|
+
children: 'Edit'
|
|
630
|
+
}),
|
|
631
|
+
isSiteEventType(String((_ref = (_action_trigger1 = action.trigger) == null ? void 0 : _action_trigger1.event) != null ? _ref : '')) ? // Test run (AGL-266): server steps only, via the dispatch API.
|
|
632
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
633
|
+
size: "small",
|
|
634
|
+
onClick: ()=>void handleTestRun(action),
|
|
635
|
+
children: 'Test'
|
|
636
|
+
}) : null,
|
|
637
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
638
|
+
size: "small",
|
|
639
|
+
onClick: ()=>setRunsFor(action),
|
|
640
|
+
children: 'Runs'
|
|
641
|
+
}),
|
|
642
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
643
|
+
size: "small",
|
|
644
|
+
color: "error",
|
|
645
|
+
onClick: handleDelete(action),
|
|
646
|
+
children: 'Delete'
|
|
647
|
+
})
|
|
648
|
+
]
|
|
649
|
+
}, action.$id);
|
|
650
|
+
}),
|
|
651
|
+
actions.length === 0 ? null : /*#__PURE__*/ _jsx(ListPagination, {
|
|
652
|
+
page: page,
|
|
653
|
+
pageSize: pageSize,
|
|
654
|
+
rowCount: visibleActions.length,
|
|
655
|
+
// The actions the card HOLDS, which is what the reader is paging
|
|
656
|
+
// through — not the collection, whose other half is the element
|
|
657
|
+
// interactions counted below.
|
|
658
|
+
count: actions.length,
|
|
659
|
+
onPageChange: setPage,
|
|
660
|
+
onPageSizeChange: setPageSize
|
|
661
|
+
}),
|
|
662
|
+
actionsTruncated ? /*#__PURE__*/ _jsx(Alert, {
|
|
663
|
+
severity: "info",
|
|
664
|
+
children: `Showing the first ${ACTION_CEILING} rows of this site’s ` + 'automations, ordered by id. There are more — both the list ' + 'above and the interaction count below describe only what was ' + 'read.'
|
|
665
|
+
}) : null,
|
|
666
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
667
|
+
direction: "row",
|
|
668
|
+
spacing: 1,
|
|
669
|
+
sx: {
|
|
670
|
+
alignSelf: 'flex-start'
|
|
671
|
+
},
|
|
672
|
+
children: [
|
|
673
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
674
|
+
size: "small",
|
|
675
|
+
color: "primary",
|
|
676
|
+
onClick: handleAdd,
|
|
677
|
+
children: 'Add action'
|
|
678
|
+
}),
|
|
679
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
680
|
+
size: "small",
|
|
681
|
+
color: "primary",
|
|
682
|
+
"aria-haspopup": "menu",
|
|
683
|
+
"aria-controls": recipesAnchor ? 'host-action-recipes' : undefined,
|
|
684
|
+
"aria-expanded": recipesAnchor ? true : undefined,
|
|
685
|
+
onClick: (event)=>setRecipesAnchor(event.currentTarget),
|
|
686
|
+
children: 'Recipes'
|
|
687
|
+
}),
|
|
688
|
+
ExtensionZone ? /*#__PURE__*/ _jsx(ExtensionZone, {
|
|
689
|
+
slot: "hostAutomations",
|
|
690
|
+
hostId: hostId,
|
|
691
|
+
orgId: org == null ? void 0 : org.$id,
|
|
692
|
+
openAction: openAction
|
|
693
|
+
}) : null
|
|
694
|
+
]
|
|
695
|
+
}),
|
|
696
|
+
/*#__PURE__*/ _jsx(Menu, {
|
|
697
|
+
id: "host-action-recipes",
|
|
698
|
+
anchorEl: recipesAnchor,
|
|
699
|
+
open: Boolean(recipesAnchor),
|
|
700
|
+
onClose: ()=>setRecipesAnchor(null),
|
|
701
|
+
children: CRM_ACTION_RECIPES.map((recipe)=>/*#__PURE__*/ _jsx(MenuItem, {
|
|
702
|
+
onClick: ()=>handleRecipe(recipe),
|
|
703
|
+
children: /*#__PURE__*/ _jsx(ListItemText, {
|
|
704
|
+
primary: recipe.title,
|
|
705
|
+
secondary: recipe.description,
|
|
706
|
+
slotProps: {
|
|
707
|
+
secondary: {
|
|
708
|
+
sx: {
|
|
709
|
+
whiteSpace: 'normal',
|
|
710
|
+
maxWidth: 360
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
})
|
|
715
|
+
}, recipe.id))
|
|
716
|
+
}),
|
|
717
|
+
elementScoped.length ? /*#__PURE__*/ _jsxs(Typography, {
|
|
718
|
+
variant: "caption",
|
|
719
|
+
color: "text.secondary",
|
|
720
|
+
children: [
|
|
721
|
+
elementScoped.length === 1 ? '1 interaction is set up on its own element — ' : `${elementScoped.length} interactions are set up on their own elements — `,
|
|
722
|
+
'open the element in the besigner to edit it. Interactions ' + 'belong to the page they are on, so they publish and roll back ' + 'with it.'
|
|
723
|
+
]
|
|
724
|
+
}) : null
|
|
725
|
+
]
|
|
726
|
+
}),
|
|
727
|
+
/*#__PURE__*/ _jsxs(Dialog, {
|
|
728
|
+
open: Boolean(draft),
|
|
729
|
+
onClose: ()=>setDraft(null),
|
|
730
|
+
maxWidth: "sm",
|
|
731
|
+
fullWidth: true,
|
|
732
|
+
children: [
|
|
733
|
+
/*#__PURE__*/ _jsx(DialogTitle, {
|
|
734
|
+
children: (draft == null ? void 0 : draft.id) ? 'Edit action' : 'Add action'
|
|
735
|
+
}),
|
|
736
|
+
/*#__PURE__*/ _jsxs(DialogContent, {
|
|
737
|
+
sx: {
|
|
738
|
+
display: 'flex',
|
|
739
|
+
flexDirection: 'column',
|
|
740
|
+
gap: 1.5,
|
|
741
|
+
pt: 1
|
|
742
|
+
},
|
|
743
|
+
children: [
|
|
744
|
+
ExtensionZone && editorTarget ? /*#__PURE__*/ _jsx(ExtensionZone, {
|
|
745
|
+
slot: "automationEditor",
|
|
746
|
+
hostId: hostId,
|
|
747
|
+
orgId: org == null ? void 0 : org.$id,
|
|
748
|
+
target: editorTarget
|
|
749
|
+
}) : null,
|
|
750
|
+
(draft == null ? void 0 : draft.recipe) ? /*#__PURE__*/ _jsx(Typography, {
|
|
751
|
+
variant: "body2",
|
|
752
|
+
color: "text.secondary",
|
|
753
|
+
children: `Started from the “${(_ref2 = (_CRM_ACTION_RECIPES_find = CRM_ACTION_RECIPES.find((recipe)=>recipe.id === draft.recipe)) == null ? void 0 : _CRM_ACTION_RECIPES_find.title) != null ? _ref2 : 'recipe'}” recipe — change anything, then save.`
|
|
754
|
+
}) : null,
|
|
755
|
+
truncatedPickers.length > 0 ? /*#__PURE__*/ _jsx(Alert, {
|
|
756
|
+
severity: "info",
|
|
757
|
+
sx: {
|
|
758
|
+
mt: 1
|
|
759
|
+
},
|
|
760
|
+
children: `Offering the first ${EDITOR_OPTION_CEILING} rows, ordered by ` + `id, for: ${truncatedPickers.join(', ')}. This site has ` + 'more, so a step target may not be listed below.'
|
|
761
|
+
}) : null,
|
|
762
|
+
/*#__PURE__*/ _jsx(TextField, {
|
|
763
|
+
label: "Name",
|
|
764
|
+
value: (_ref3 = draft == null ? void 0 : draft.name) != null ? _ref3 : '',
|
|
765
|
+
onChange: (event)=>patch((previous)=>_extends({}, previous, {
|
|
766
|
+
name: event.target.value
|
|
767
|
+
})),
|
|
768
|
+
size: "small",
|
|
769
|
+
autoFocus: true,
|
|
770
|
+
sx: {
|
|
771
|
+
mt: 1
|
|
772
|
+
}
|
|
773
|
+
}),
|
|
774
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
775
|
+
direction: "row",
|
|
776
|
+
spacing: 1,
|
|
777
|
+
children: [
|
|
778
|
+
/*#__PURE__*/ _jsxs(TextField, {
|
|
779
|
+
select: true,
|
|
780
|
+
label: "Trigger event",
|
|
781
|
+
value: (_ref4 = draft == null ? void 0 : draft.trigger.event) != null ? _ref4 : '',
|
|
782
|
+
onChange: (event)=>patch((previous)=>_extends({}, previous, {
|
|
783
|
+
trigger: _extends({}, previous.trigger, {
|
|
784
|
+
event: event.target.value
|
|
785
|
+
})
|
|
786
|
+
})),
|
|
787
|
+
size: "small",
|
|
788
|
+
sx: {
|
|
789
|
+
minWidth: 180
|
|
790
|
+
},
|
|
791
|
+
children: [
|
|
792
|
+
HOST_EVENT_TYPES.map((eventType)=>/*#__PURE__*/ _jsx(MenuItem, {
|
|
793
|
+
value: eventType,
|
|
794
|
+
children: hostEventLabel(eventType)
|
|
795
|
+
}, eventType)),
|
|
796
|
+
SITE_EVENT_TYPES.map((eventType)=>/*#__PURE__*/ _jsx(MenuItem, {
|
|
797
|
+
value: eventType,
|
|
798
|
+
children: `${eventType} (on page)`
|
|
799
|
+
}, eventType)),
|
|
800
|
+
/*#__PURE__*/ _jsx(MenuItem, {
|
|
801
|
+
value: CUSTOM_EVENT_VALUE,
|
|
802
|
+
children: 'Custom event…'
|
|
803
|
+
})
|
|
804
|
+
]
|
|
805
|
+
}),
|
|
806
|
+
(draft == null ? void 0 : draft.trigger.event) === CUSTOM_EVENT_VALUE ? /*#__PURE__*/ _jsx(TextField, {
|
|
807
|
+
label: "Custom event name",
|
|
808
|
+
value: (_ref5 = draft == null ? void 0 : draft.customEvent) != null ? _ref5 : '',
|
|
809
|
+
onChange: (event)=>patch((previous)=>_extends({}, previous, {
|
|
810
|
+
customEvent: event.target.value
|
|
811
|
+
})),
|
|
812
|
+
size: "small",
|
|
813
|
+
sx: {
|
|
814
|
+
flex: 1
|
|
815
|
+
}
|
|
816
|
+
}) : /*#__PURE__*/ _jsx(TextField, {
|
|
817
|
+
label: "Filter (optional)",
|
|
818
|
+
placeholder: 'path == "/pricing"',
|
|
819
|
+
// What the expression — and the conditions below — can name
|
|
820
|
+
// for this event; nothing for an event whose payload is not
|
|
821
|
+
// written down, rather than a guess.
|
|
822
|
+
helperText: (_hostEventPayloadHint = hostEventPayloadHint(draft == null ? void 0 : draft.trigger.event)) != null ? _hostEventPayloadHint : undefined,
|
|
823
|
+
value: (_ref6 = draft == null ? void 0 : draft.trigger.filter) != null ? _ref6 : '',
|
|
824
|
+
onChange: (event)=>patch((previous)=>_extends({}, previous, {
|
|
825
|
+
trigger: _extends({}, previous.trigger, {
|
|
826
|
+
filter: event.target.value
|
|
827
|
+
})
|
|
828
|
+
})),
|
|
829
|
+
size: "small",
|
|
830
|
+
sx: {
|
|
831
|
+
flex: 1
|
|
832
|
+
}
|
|
833
|
+
})
|
|
834
|
+
]
|
|
835
|
+
}),
|
|
836
|
+
((_ref7 = draft == null ? void 0 : draft.conditionRows) != null ? _ref7 : []).map((row, index)=>{
|
|
837
|
+
var _ref, _ref1;
|
|
838
|
+
return /*#__PURE__*/ _jsxs(Stack, {
|
|
839
|
+
direction: "row",
|
|
840
|
+
spacing: 1,
|
|
841
|
+
sx: {
|
|
842
|
+
alignItems: 'center'
|
|
843
|
+
},
|
|
844
|
+
children: [
|
|
845
|
+
index > 0 ? /*#__PURE__*/ _jsx(Typography, {
|
|
846
|
+
variant: "caption",
|
|
847
|
+
color: "text.secondary",
|
|
848
|
+
children: (draft == null ? void 0 : draft.conditionCombinator) === 'or' ? 'or' : 'and'
|
|
849
|
+
}) : null,
|
|
850
|
+
/*#__PURE__*/ _jsxs(TextField, {
|
|
851
|
+
select: true,
|
|
852
|
+
label: index === 0 ? 'Only run when' : 'Condition',
|
|
853
|
+
value: row.op,
|
|
854
|
+
onChange: (event)=>patch((previous)=>_extends({}, previous, {
|
|
855
|
+
conditionRows: previous.conditionRows.map((previousRow, index2)=>index2 === index ? _extends({}, previousRow, {
|
|
856
|
+
op: event.target.value
|
|
857
|
+
}) : previousRow)
|
|
858
|
+
})),
|
|
859
|
+
size: "small",
|
|
860
|
+
sx: {
|
|
861
|
+
minWidth: 180
|
|
862
|
+
},
|
|
863
|
+
children: [
|
|
864
|
+
((_ref = draft == null ? void 0 : draft.conditionRows.length) != null ? _ref : 0) === 1 ? /*#__PURE__*/ _jsx(MenuItem, {
|
|
865
|
+
value: "",
|
|
866
|
+
children: 'Always (no condition)'
|
|
867
|
+
}) : null,
|
|
868
|
+
/*#__PURE__*/ _jsx(MenuItem, {
|
|
869
|
+
value: "notEmpty",
|
|
870
|
+
children: 'A field is not empty'
|
|
871
|
+
}),
|
|
872
|
+
/*#__PURE__*/ _jsx(MenuItem, {
|
|
873
|
+
value: "equals",
|
|
874
|
+
children: 'A field equals…'
|
|
875
|
+
}),
|
|
876
|
+
/*#__PURE__*/ _jsx(MenuItem, {
|
|
877
|
+
value: "contains",
|
|
878
|
+
children: 'A field contains…'
|
|
879
|
+
})
|
|
880
|
+
]
|
|
881
|
+
}),
|
|
882
|
+
row.op ? /*#__PURE__*/ _jsx(TextField, {
|
|
883
|
+
label: "Field",
|
|
884
|
+
placeholder: "subscribe",
|
|
885
|
+
value: row.field,
|
|
886
|
+
onChange: (event)=>patch((previous)=>_extends({}, previous, {
|
|
887
|
+
conditionRows: previous.conditionRows.map((previousRow, index2)=>index2 === index ? _extends({}, previousRow, {
|
|
888
|
+
field: event.target.value
|
|
889
|
+
}) : previousRow)
|
|
890
|
+
})),
|
|
891
|
+
size: "small",
|
|
892
|
+
sx: {
|
|
893
|
+
flex: 1
|
|
894
|
+
}
|
|
895
|
+
}) : null,
|
|
896
|
+
row.op === 'equals' || row.op === 'contains' ? /*#__PURE__*/ _jsx(TextField, _extends({
|
|
897
|
+
label: "Value",
|
|
898
|
+
placeholder: "Yes"
|
|
899
|
+
}, placeholderState(row.value, 'Replace the placeholder with the value to match'), {
|
|
900
|
+
value: row.value,
|
|
901
|
+
onChange: (event)=>patch((previous)=>_extends({}, previous, {
|
|
902
|
+
conditionRows: previous.conditionRows.map((previousRow, index2)=>index2 === index ? _extends({}, previousRow, {
|
|
903
|
+
value: event.target.value
|
|
904
|
+
}) : previousRow)
|
|
905
|
+
})),
|
|
906
|
+
size: "small",
|
|
907
|
+
sx: {
|
|
908
|
+
flex: 1
|
|
909
|
+
}
|
|
910
|
+
})) : null,
|
|
911
|
+
((_ref1 = draft == null ? void 0 : draft.conditionRows.length) != null ? _ref1 : 0) > 1 ? /*#__PURE__*/ _jsx(IconButton, {
|
|
912
|
+
size: "small",
|
|
913
|
+
"aria-label": "remove condition",
|
|
914
|
+
onClick: ()=>patch((previous)=>_extends({}, previous, {
|
|
915
|
+
conditionRows: previous.conditionRows.length > 1 ? previous.conditionRows.filter((_, index2)=>index2 !== index) : [
|
|
916
|
+
EMPTY_CONDITION_ROW
|
|
917
|
+
]
|
|
918
|
+
})),
|
|
919
|
+
children: '×'
|
|
920
|
+
}) : null
|
|
921
|
+
]
|
|
922
|
+
}, index);
|
|
923
|
+
}),
|
|
924
|
+
(draft == null ? void 0 : draft.conditionRows.every((row)=>row.op)) ? /*#__PURE__*/ _jsxs(Stack, {
|
|
925
|
+
direction: "row",
|
|
926
|
+
spacing: 1,
|
|
927
|
+
sx: {
|
|
928
|
+
alignItems: 'center'
|
|
929
|
+
},
|
|
930
|
+
children: [
|
|
931
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
932
|
+
size: "small",
|
|
933
|
+
disabled: ((_ref8 = draft == null ? void 0 : draft.conditionRows.length) != null ? _ref8 : 0) >= ACTION_MAX_CONDITIONS,
|
|
934
|
+
onClick: ()=>patch((previous)=>_extends({}, previous, {
|
|
935
|
+
conditionRows: [
|
|
936
|
+
...previous.conditionRows,
|
|
937
|
+
// New rows start on the simplest operator so the
|
|
938
|
+
// field input is immediately visible.
|
|
939
|
+
{
|
|
940
|
+
op: 'notEmpty',
|
|
941
|
+
field: '',
|
|
942
|
+
value: ''
|
|
943
|
+
}
|
|
944
|
+
]
|
|
945
|
+
})),
|
|
946
|
+
children: 'Add condition'
|
|
947
|
+
}),
|
|
948
|
+
((_ref9 = draft == null ? void 0 : draft.conditionRows.length) != null ? _ref9 : 0) >= 2 ? // AND/OR combinator (AGL-565); applies to every row.
|
|
949
|
+
/*#__PURE__*/ _jsxs(TextField, {
|
|
950
|
+
select: true,
|
|
951
|
+
label: "Match",
|
|
952
|
+
value: (_ref10 = draft == null ? void 0 : draft.conditionCombinator) != null ? _ref10 : 'and',
|
|
953
|
+
onChange: (event)=>patch((previous)=>_extends({}, previous, {
|
|
954
|
+
conditionCombinator: event.target.value
|
|
955
|
+
})),
|
|
956
|
+
size: "small",
|
|
957
|
+
sx: {
|
|
958
|
+
minWidth: 220
|
|
959
|
+
},
|
|
960
|
+
children: [
|
|
961
|
+
/*#__PURE__*/ _jsx(MenuItem, {
|
|
962
|
+
value: "and",
|
|
963
|
+
children: 'All conditions match (AND)'
|
|
964
|
+
}),
|
|
965
|
+
/*#__PURE__*/ _jsx(MenuItem, {
|
|
966
|
+
value: "or",
|
|
967
|
+
children: 'Any condition matches (OR)'
|
|
968
|
+
})
|
|
969
|
+
]
|
|
970
|
+
}) : null
|
|
971
|
+
]
|
|
972
|
+
}) : null,
|
|
973
|
+
isSiteEventType((_ref11 = draft == null ? void 0 : draft.trigger.event) != null ? _ref11 : '') ? // Site-event config (AGL-256): what/where the trigger watches.
|
|
974
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
975
|
+
direction: "row",
|
|
976
|
+
spacing: 1,
|
|
977
|
+
children: [
|
|
978
|
+
ELEMENT_SCOPED_SITE_EVENTS.includes((_ref12 = draft == null ? void 0 : draft.trigger.event) != null ? _ref12 : '') ? /*#__PURE__*/ _jsx(TextField, {
|
|
979
|
+
label: "CSS selector",
|
|
980
|
+
placeholder: "#pricing-table",
|
|
981
|
+
value: (_ref13 = draft == null ? void 0 : draft.trigger.selector) != null ? _ref13 : '',
|
|
982
|
+
onChange: (event)=>patch((previous)=>_extends({}, previous, {
|
|
983
|
+
trigger: _extends({}, previous.trigger, {
|
|
984
|
+
selector: event.target.value
|
|
985
|
+
})
|
|
986
|
+
})),
|
|
987
|
+
size: "small",
|
|
988
|
+
sx: {
|
|
989
|
+
flex: 1
|
|
990
|
+
}
|
|
991
|
+
}) : null,
|
|
992
|
+
[
|
|
993
|
+
'scrollDepth',
|
|
994
|
+
'timeOnPage'
|
|
995
|
+
].includes((_ref14 = draft == null ? void 0 : draft.trigger.event) != null ? _ref14 : '') ? /*#__PURE__*/ _jsx(TextField, {
|
|
996
|
+
type: "number",
|
|
997
|
+
label: (draft == null ? void 0 : draft.trigger.event) === 'scrollDepth' ? 'Scroll %' : 'Seconds',
|
|
998
|
+
value: (_ref15 = draft == null ? void 0 : draft.trigger.threshold) != null ? _ref15 : '',
|
|
999
|
+
onChange: (event)=>patch((previous)=>_extends({}, previous, {
|
|
1000
|
+
trigger: _extends({}, previous.trigger, {
|
|
1001
|
+
threshold: Number(event.target.value)
|
|
1002
|
+
})
|
|
1003
|
+
})),
|
|
1004
|
+
size: "small",
|
|
1005
|
+
sx: {
|
|
1006
|
+
width: 120
|
|
1007
|
+
}
|
|
1008
|
+
}) : null,
|
|
1009
|
+
/*#__PURE__*/ _jsx(TextField, {
|
|
1010
|
+
label: "Only on pages (optional)",
|
|
1011
|
+
placeholder: "/pricing or /blog/*",
|
|
1012
|
+
value: (_ref16 = draft == null ? void 0 : draft.trigger.pathPattern) != null ? _ref16 : '',
|
|
1013
|
+
onChange: (event)=>patch((previous)=>_extends({}, previous, {
|
|
1014
|
+
trigger: _extends({}, previous.trigger, {
|
|
1015
|
+
pathPattern: event.target.value
|
|
1016
|
+
})
|
|
1017
|
+
})),
|
|
1018
|
+
size: "small",
|
|
1019
|
+
sx: {
|
|
1020
|
+
flex: 1
|
|
1021
|
+
}
|
|
1022
|
+
}),
|
|
1023
|
+
/*#__PURE__*/ _jsxs(TextField, {
|
|
1024
|
+
select: true,
|
|
1025
|
+
label: "Frequency",
|
|
1026
|
+
size: "small",
|
|
1027
|
+
sx: {
|
|
1028
|
+
minWidth: 170
|
|
1029
|
+
},
|
|
1030
|
+
value: (draft == null ? void 0 : draft.trigger.oncePerVisitor) ? 'visitor' : (draft == null ? void 0 : draft.trigger.oncePerSession) ? 'session' : Number(draft == null ? void 0 : draft.trigger.cooldownMinutes) >= 1 ? 'cooldown' : (draft == null ? void 0 : draft.trigger.everyTime) ? 'every' : '',
|
|
1031
|
+
onChange: (event)=>{
|
|
1032
|
+
const mode = event.target.value;
|
|
1033
|
+
patch((previous)=>_extends({}, previous, {
|
|
1034
|
+
trigger: _extends({}, previous.trigger, {
|
|
1035
|
+
// Every occurrence (AGL-562): repeatable UI
|
|
1036
|
+
// choreography (menu/drawer toggles).
|
|
1037
|
+
everyTime: mode === 'every',
|
|
1038
|
+
oncePerVisitor: mode === 'visitor',
|
|
1039
|
+
oncePerSession: mode === 'session',
|
|
1040
|
+
cooldownMinutes: mode === 'cooldown' ? Number(previous.trigger.cooldownMinutes) >= 1 ? Number(previous.trigger.cooldownMinutes) : 60 : undefined
|
|
1041
|
+
})
|
|
1042
|
+
}));
|
|
1043
|
+
},
|
|
1044
|
+
children: [
|
|
1045
|
+
/*#__PURE__*/ _jsx(MenuItem, {
|
|
1046
|
+
value: "",
|
|
1047
|
+
children: 'Every matching pageview'
|
|
1048
|
+
}),
|
|
1049
|
+
/*#__PURE__*/ _jsx(MenuItem, {
|
|
1050
|
+
value: "every",
|
|
1051
|
+
children: 'Every occurrence (repeatable)'
|
|
1052
|
+
}),
|
|
1053
|
+
/*#__PURE__*/ _jsx(MenuItem, {
|
|
1054
|
+
value: "session",
|
|
1055
|
+
children: 'Once per session'
|
|
1056
|
+
}),
|
|
1057
|
+
/*#__PURE__*/ _jsx(MenuItem, {
|
|
1058
|
+
value: "visitor",
|
|
1059
|
+
children: 'Once per visitor'
|
|
1060
|
+
}),
|
|
1061
|
+
/*#__PURE__*/ _jsx(MenuItem, {
|
|
1062
|
+
value: "cooldown",
|
|
1063
|
+
children: 'With a cooldown'
|
|
1064
|
+
})
|
|
1065
|
+
]
|
|
1066
|
+
}),
|
|
1067
|
+
Number(draft == null ? void 0 : draft.trigger.cooldownMinutes) >= 1 && !(draft == null ? void 0 : draft.trigger.oncePerVisitor) && !(draft == null ? void 0 : draft.trigger.oncePerSession) ? /*#__PURE__*/ _jsx(TextField, {
|
|
1068
|
+
type: "number",
|
|
1069
|
+
label: "Cooldown (minutes)",
|
|
1070
|
+
size: "small",
|
|
1071
|
+
sx: {
|
|
1072
|
+
width: 150
|
|
1073
|
+
},
|
|
1074
|
+
value: (_ref17 = draft == null ? void 0 : draft.trigger.cooldownMinutes) != null ? _ref17 : 60,
|
|
1075
|
+
onChange: (event)=>patch((previous)=>_extends({}, previous, {
|
|
1076
|
+
trigger: _extends({}, previous.trigger, {
|
|
1077
|
+
cooldownMinutes: Number(event.target.value)
|
|
1078
|
+
})
|
|
1079
|
+
}))
|
|
1080
|
+
}) : null
|
|
1081
|
+
]
|
|
1082
|
+
}) : null,
|
|
1083
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
1084
|
+
variant: "overline",
|
|
1085
|
+
color: "text.secondary",
|
|
1086
|
+
children: 'Steps (run in order)'
|
|
1087
|
+
}),
|
|
1088
|
+
((_ref18 = draft == null ? void 0 : draft.steps) != null ? _ref18 : []).map((step, index)=>/*#__PURE__*/ _jsx(AutomationStepFields, {
|
|
1089
|
+
step: step,
|
|
1090
|
+
index: index,
|
|
1091
|
+
kind: step.type,
|
|
1092
|
+
kinds: ACTION_STEP_KINDS,
|
|
1093
|
+
stepForKind: (value)=>defaultStep(value),
|
|
1094
|
+
pickers: stepPickers,
|
|
1095
|
+
onSteps: (update)=>patch((previous)=>_extends({}, previous, {
|
|
1096
|
+
steps: update(previous.steps)
|
|
1097
|
+
}))
|
|
1098
|
+
}, index)),
|
|
1099
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
1100
|
+
size: "small",
|
|
1101
|
+
sx: {
|
|
1102
|
+
alignSelf: 'flex-start'
|
|
1103
|
+
},
|
|
1104
|
+
disabled: ((_ref19 = draft == null ? void 0 : draft.steps.length) != null ? _ref19 : 0) >= ACTION_MAX_STEPS,
|
|
1105
|
+
onClick: ()=>patch((previous)=>_extends({}, previous, {
|
|
1106
|
+
steps: [
|
|
1107
|
+
...previous.steps,
|
|
1108
|
+
defaultStep('siteAlert')
|
|
1109
|
+
]
|
|
1110
|
+
})),
|
|
1111
|
+
children: 'Add step'
|
|
1112
|
+
})
|
|
1113
|
+
]
|
|
1114
|
+
}),
|
|
1115
|
+
/*#__PURE__*/ _jsxs(DialogActions, {
|
|
1116
|
+
children: [
|
|
1117
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
1118
|
+
onClick: ()=>setDraft(null),
|
|
1119
|
+
children: 'Cancel'
|
|
1120
|
+
}),
|
|
1121
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
1122
|
+
variant: "contained",
|
|
1123
|
+
color: "primary",
|
|
1124
|
+
disabled: !(draft == null ? void 0 : draft.name.trim()),
|
|
1125
|
+
onClick: handleSave,
|
|
1126
|
+
children: 'Save action'
|
|
1127
|
+
})
|
|
1128
|
+
]
|
|
1129
|
+
})
|
|
1130
|
+
]
|
|
1131
|
+
}),
|
|
1132
|
+
/*#__PURE__*/ _jsxs(Dialog, {
|
|
1133
|
+
open: Boolean(formPickFor),
|
|
1134
|
+
onClose: ()=>setFormPickFor(null),
|
|
1135
|
+
maxWidth: "xs",
|
|
1136
|
+
fullWidth: true,
|
|
1137
|
+
children: [
|
|
1138
|
+
/*#__PURE__*/ _jsx(DialogTitle, {
|
|
1139
|
+
children: (_ref20 = formPickFor == null ? void 0 : formPickFor.title) != null ? _ref20 : ''
|
|
1140
|
+
}),
|
|
1141
|
+
/*#__PURE__*/ _jsxs(DialogContent, {
|
|
1142
|
+
sx: {
|
|
1143
|
+
display: 'flex',
|
|
1144
|
+
flexDirection: 'column',
|
|
1145
|
+
gap: 1.5,
|
|
1146
|
+
pt: 1
|
|
1147
|
+
},
|
|
1148
|
+
children: [
|
|
1149
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
1150
|
+
variant: "body2",
|
|
1151
|
+
color: "text.secondary",
|
|
1152
|
+
children: 'Pick the form whose new contacts get the tag. The action opens ' + 'ready to edit; nothing is saved until you save it.'
|
|
1153
|
+
}),
|
|
1154
|
+
formsTruncated ? /*#__PURE__*/ _jsx(Alert, {
|
|
1155
|
+
severity: "info",
|
|
1156
|
+
children: `Offering the first ${EDITOR_OPTION_CEILING} forms, ordered by ` + 'id. This site has more, so the form you want may not be listed.'
|
|
1157
|
+
}) : null,
|
|
1158
|
+
formOptions.length ? /*#__PURE__*/ _jsx(TextField, {
|
|
1159
|
+
select: true,
|
|
1160
|
+
label: "Form",
|
|
1161
|
+
value: pickedFormId,
|
|
1162
|
+
onChange: (event)=>setPickedFormId(event.target.value),
|
|
1163
|
+
size: "small",
|
|
1164
|
+
sx: {
|
|
1165
|
+
mt: 1
|
|
1166
|
+
},
|
|
1167
|
+
children: formOptions.map((option)=>/*#__PURE__*/ _jsx(MenuItem, {
|
|
1168
|
+
value: option.id,
|
|
1169
|
+
children: option.name
|
|
1170
|
+
}, option.id))
|
|
1171
|
+
}) : /*#__PURE__*/ _jsx(Typography, {
|
|
1172
|
+
variant: "body2",
|
|
1173
|
+
color: "text.secondary",
|
|
1174
|
+
children: 'This site has no forms yet. Add one in the besigner, then come back.'
|
|
1175
|
+
})
|
|
1176
|
+
]
|
|
1177
|
+
}),
|
|
1178
|
+
/*#__PURE__*/ _jsxs(DialogActions, {
|
|
1179
|
+
children: [
|
|
1180
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
1181
|
+
color: "inherit",
|
|
1182
|
+
onClick: ()=>setFormPickFor(null),
|
|
1183
|
+
children: 'Cancel'
|
|
1184
|
+
}),
|
|
1185
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
1186
|
+
variant: "contained",
|
|
1187
|
+
disabled: !pickedFormId,
|
|
1188
|
+
onClick: handleFormPicked,
|
|
1189
|
+
children: 'Use recipe'
|
|
1190
|
+
})
|
|
1191
|
+
]
|
|
1192
|
+
})
|
|
1193
|
+
]
|
|
1194
|
+
}),
|
|
1195
|
+
/*#__PURE__*/ _jsxs(Dialog, {
|
|
1196
|
+
open: Boolean(runsFor),
|
|
1197
|
+
onClose: ()=>setRunsFor(null),
|
|
1198
|
+
maxWidth: "sm",
|
|
1199
|
+
fullWidth: true,
|
|
1200
|
+
children: [
|
|
1201
|
+
/*#__PURE__*/ _jsx(DialogTitle, {
|
|
1202
|
+
children: `Runs — ${(_ref21 = runsFor == null ? void 0 : runsFor.name) != null ? _ref21 : ''}`
|
|
1203
|
+
}),
|
|
1204
|
+
/*#__PURE__*/ _jsx(DialogContent, {
|
|
1205
|
+
children: runsFor ? /*#__PURE__*/ _jsx(HostRunHistoryCard, {
|
|
1206
|
+
hostId: hostId,
|
|
1207
|
+
orgId: org == null ? void 0 : org.$id,
|
|
1208
|
+
targetId: runsFor.$id,
|
|
1209
|
+
targetType: "action",
|
|
1210
|
+
targetName: (_runsFor_name = runsFor.name) != null ? _runsFor_name : '',
|
|
1211
|
+
header: "Recent runs"
|
|
1212
|
+
}) : null
|
|
1213
|
+
}),
|
|
1214
|
+
/*#__PURE__*/ _jsx(DialogActions, {
|
|
1215
|
+
children: /*#__PURE__*/ _jsx(Button, {
|
|
1216
|
+
color: "inherit",
|
|
1217
|
+
onClick: ()=>setRunsFor(null),
|
|
1218
|
+
children: 'Close'
|
|
1219
|
+
})
|
|
1220
|
+
})
|
|
1221
|
+
]
|
|
1222
|
+
})
|
|
1223
|
+
]
|
|
1224
|
+
});
|
|
1225
|
+
}
|
|
1226
|
+
HostActionsCard.displayName = 'HostActionsCard';
|
|
1227
|
+
export default HostActionsCard;
|
|
1228
|
+
|
|
1229
|
+
//# sourceMappingURL=host-actions-card.component.js.map
|