@delmaredigital/payload-experiments 0.2.0-rc.3 → 0.2.0-rc.5
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 +22 -0
- package/dist/admin/PreviewLinks.js +8 -4
- package/dist/admin/TestsDashboardCards.d.ts +3 -0
- package/dist/admin/TestsDashboardCards.js +52 -0
- package/dist/admin/TestsNavLink.d.ts +3 -1
- package/dist/admin/TestsNavLink.js +62 -18
- package/dist/admin/actions/ConfirmDialog.d.ts +1 -0
- package/dist/admin/actions/ConfirmDialog.js +7 -5
- package/dist/admin/components/ChipsInput.d.ts +12 -0
- package/dist/admin/components/ChipsInput.js +96 -0
- package/dist/admin/components/RootViewMessage.d.ts +5 -0
- package/dist/admin/components/RootViewMessage.js +15 -0
- package/dist/admin/components/chipsInput.css +11 -0
- package/dist/admin/copy/audience.d.ts +4 -0
- package/dist/admin/copy/audience.js +4 -0
- package/dist/admin/copy/goals.d.ts +44 -0
- package/dist/admin/copy/goals.js +46 -0
- package/dist/admin/copy/review.d.ts +1 -0
- package/dist/admin/copy/review.js +1 -0
- package/dist/admin/copy/surface.d.ts +4 -0
- package/dist/admin/copy/surface.js +4 -0
- package/dist/admin/dashboard.css +35 -0
- package/dist/admin/paths.d.ts +1 -0
- package/dist/admin/paths.js +1 -0
- package/dist/admin/previewLinks.model.d.ts +9 -0
- package/dist/admin/previewLinks.model.js +34 -0
- package/dist/admin/results/ArmTable.js +2 -1
- package/dist/admin/results/CappedCell.js +2 -1
- package/dist/admin/results/CiChart.js +8 -7
- package/dist/admin/results/DataQualityPanel.js +3 -2
- package/dist/admin/results/VerdictCard.js +3 -2
- package/dist/admin/styles.css +8 -8
- package/dist/admin/styles.d.ts +1 -0
- package/dist/admin/styles.js +1 -0
- package/dist/admin/surface/SurfaceShell.d.ts +1 -0
- package/dist/admin/surface/SurfaceShell.js +1 -0
- package/dist/admin/surface/TestsHome.js +1 -1
- package/dist/admin/surface/TestsSurface.js +10 -1
- package/dist/admin/surface/goals/GoalForm.d.ts +7 -0
- package/dist/admin/surface/goals/GoalForm.js +470 -0
- package/dist/admin/surface/goals/GoalsBreadcrumb.d.ts +5 -0
- package/dist/admin/surface/goals/GoalsBreadcrumb.js +47 -0
- package/dist/admin/surface/goals/GoalsList.d.ts +6 -0
- package/dist/admin/surface/goals/GoalsList.js +135 -0
- package/dist/admin/surface/goals/data.d.ts +3 -0
- package/dist/admin/surface/goals/data.js +24 -0
- package/dist/admin/surface/goals/goals.css +15 -0
- package/dist/admin/surface/goals/goals.model.d.ts +30 -0
- package/dist/admin/surface/goals/goals.model.js +26 -0
- package/dist/admin/surface/newTest/newTest.css +6 -6
- package/dist/admin/surface/readiness/Banner.js +5 -4
- package/dist/admin/surface/results/ResultsBody.js +3 -2
- package/dist/admin/surface/setting/setting.css +11 -11
- package/dist/admin/surface/surface.css +29 -29
- package/dist/admin/surface/test/GoalSection.js +32 -1
- package/dist/admin/surface/test/TargetingSection.js +27 -10
- package/dist/admin/surface/test/VersionsSection.js +6 -5
- package/dist/admin/surface/test/testPage.css +12 -12
- package/dist/admin/targeting/targeting.model.d.ts +6 -1
- package/dist/admin/targeting/targeting.model.js +18 -2
- package/dist/admin/theme.css +44 -0
- package/dist/admin/theme.d.ts +50 -0
- package/dist/admin/theme.js +49 -0
- package/dist/admin/views/ExperimentsRootView.js +18 -18
- package/dist/collections/events.js +1 -0
- package/dist/collections/identities.js +1 -0
- package/dist/collections/layers.js +1 -0
- package/dist/collections/metricValidation.d.ts +3 -0
- package/dist/collections/metricValidation.js +9 -0
- package/dist/collections/metrics.js +4 -5
- package/dist/collections/phases.js +1 -0
- package/dist/collections/snapshots.js +1 -0
- package/dist/exports/adminClient.d.ts +1 -0
- package/dist/exports/adminClient.js +1 -0
- package/dist/globals/state.js +1 -0
- package/dist/plugin.js +8 -2
- package/dist/puck/editor/CreateExperimentDrawer.d.ts +1 -0
- package/dist/puck/editor/CreateExperimentDrawer.js +34 -25
- package/dist/puck/editor/ExperimentSlotEditor.d.ts +1 -0
- package/dist/puck/editor/ExperimentSlotEditor.js +137 -78
- package/dist/puck/editor/copy.d.ts +3 -1
- package/dist/puck/editor/copy.js +3 -1
- package/dist/puck/editor/editor.css +302 -0
- package/dist/types/surface.d.ts +2 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -466,6 +466,13 @@ for these patterns.
|
|
|
466
466
|
|
|
467
467
|
## Admin and client
|
|
468
468
|
|
|
469
|
+
The sidebar includes a **Tests** group with **All tests** and **Goals**. The
|
|
470
|
+
dashboard also includes **Tests** and **Goals** cards above its widgets. Goals
|
|
471
|
+
appears only for users who can manage tests. Both additions follow the host's
|
|
472
|
+
admin route and keep host-provided navigation and dashboard entries first.
|
|
473
|
+
After upgrading, run `payload generate:importmap` to register the dashboard card
|
|
474
|
+
component.
|
|
475
|
+
|
|
469
476
|
### Client assignments and exposure
|
|
470
477
|
|
|
471
478
|
Read assignments on the server and pass them to `ExperimentsProvider` from
|
|
@@ -530,6 +537,21 @@ components: { ExperimentSlot }
|
|
|
530
537
|
components: { ExperimentSlot: ExperimentSlotEditor }
|
|
531
538
|
```
|
|
532
539
|
|
|
540
|
+
Load the editor stylesheet inside the Puck iframe. With Tailwind v4, add the
|
|
541
|
+
package import **after** Tailwind in the CSS that builds your editor iframe
|
|
542
|
+
stylesheet (the stylesheet listed in payload-puck's `editorStylesheets`):
|
|
543
|
+
|
|
544
|
+
```css
|
|
545
|
+
@import "tailwindcss";
|
|
546
|
+
@import "@delmaredigital/payload-experiments/puck/editor.css";
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
Alternatively, serve the exported `puck/editor.css` file from a public URL and
|
|
550
|
+
append that URL to payload-puck's `editorStylesheets`, after the host stylesheet.
|
|
551
|
+
The editor CSS includes its own light and dark palette, selected by
|
|
552
|
+
`data-theme="dark"` or the `dark` class on the iframe's `<html>`. The client component's CSS import alone
|
|
553
|
+
does not reach an iframe configured with `syncHostStyles: false`.
|
|
554
|
+
|
|
533
555
|
Resolve the Puck data on the server with `resolveExperimentData` before
|
|
534
556
|
rendering it. The render component shows only the selected group's slot. The
|
|
535
557
|
editor lets you select a block experiment, edit each group's content, copy the
|
|
@@ -6,6 +6,7 @@ import { armLabel } from './labels.js';
|
|
|
6
6
|
import { useApi } from './useApi.js';
|
|
7
7
|
import { REVIEW_COPY as C } from './copy/review.js';
|
|
8
8
|
import { API_MESSAGES } from './copy/common.js';
|
|
9
|
+
import { previewPages } from './previewLinks.model.js';
|
|
9
10
|
export function PreviewLinks({ experimentId, placements, arms }) {
|
|
10
11
|
const api = useApi();
|
|
11
12
|
const inFlight = useRef(false);
|
|
@@ -39,10 +40,13 @@ export function PreviewLinks({ experimentId, placements, arms }) {
|
|
|
39
40
|
return /*#__PURE__*/ _jsxs("div", {
|
|
40
41
|
className: "xp-preview-links",
|
|
41
42
|
children: [
|
|
42
|
-
placements.map((placement)=>/*#__PURE__*/ _jsxs("div", {
|
|
43
|
+
previewPages(placements).map((placement)=>/*#__PURE__*/ _jsxs("div", {
|
|
43
44
|
children: [
|
|
44
|
-
/*#__PURE__*/
|
|
45
|
-
children:
|
|
45
|
+
/*#__PURE__*/ _jsxs("p", {
|
|
46
|
+
children: [
|
|
47
|
+
placement.title,
|
|
48
|
+
placement.slotCount > 1 ? ` ${C.placedTimes(placement.slotCount)}` : null
|
|
49
|
+
]
|
|
46
50
|
}),
|
|
47
51
|
placement.url ? arms.filter((arm)=>arm.kind !== 'holdout' && arm.key !== 'holdout').map((arm)=>/*#__PURE__*/ _jsxs(Button, {
|
|
48
52
|
disabled: busy,
|
|
@@ -59,7 +63,7 @@ export function PreviewLinks({ experimentId, placements, arms }) {
|
|
|
59
63
|
children: C.noPreview
|
|
60
64
|
})
|
|
61
65
|
]
|
|
62
|
-
},
|
|
66
|
+
}, placement.key)),
|
|
63
67
|
error ? /*#__PURE__*/ _jsx("p", {
|
|
64
68
|
role: "alert",
|
|
65
69
|
children: error
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import './theme.css';
|
|
4
|
+
import './dashboard.css';
|
|
5
|
+
import { Card, useAuth, useConfig } from '@payloadcms/ui';
|
|
6
|
+
import { formatAdminURL } from 'payload/shared';
|
|
7
|
+
import { SURFACE_COPY } from './copy/surface.js';
|
|
8
|
+
export function TestsDashboardCards() {
|
|
9
|
+
const { config } = useConfig();
|
|
10
|
+
const { permissions } = useAuth();
|
|
11
|
+
const testPermissions = permissions?.collections?.['xp-experiments'];
|
|
12
|
+
const canManage = testPermissions?.update === true;
|
|
13
|
+
if (!canManage) return null;
|
|
14
|
+
const href = (path)=>formatAdminURL({
|
|
15
|
+
adminRoute: config.routes.admin,
|
|
16
|
+
path
|
|
17
|
+
});
|
|
18
|
+
return /*#__PURE__*/ _jsxs("section", {
|
|
19
|
+
className: "xp-dash-group",
|
|
20
|
+
"aria-labelledby": "xp-dash-title",
|
|
21
|
+
children: [
|
|
22
|
+
/*#__PURE__*/ _jsx("h2", {
|
|
23
|
+
className: "xp-dash-label",
|
|
24
|
+
id: "xp-dash-title",
|
|
25
|
+
children: SURFACE_COPY.title
|
|
26
|
+
}),
|
|
27
|
+
/*#__PURE__*/ _jsxs("ul", {
|
|
28
|
+
className: "xp-dash-card-list",
|
|
29
|
+
children: [
|
|
30
|
+
/*#__PURE__*/ _jsx("li", {
|
|
31
|
+
children: /*#__PURE__*/ _jsx(Card, {
|
|
32
|
+
title: SURFACE_COPY.navAllTests,
|
|
33
|
+
titleAs: "h3",
|
|
34
|
+
buttonAriaLabel: SURFACE_COPY.dashShowTests,
|
|
35
|
+
href: href('/experiments'),
|
|
36
|
+
id: "card-xp-tests"
|
|
37
|
+
})
|
|
38
|
+
}),
|
|
39
|
+
/*#__PURE__*/ _jsx("li", {
|
|
40
|
+
children: /*#__PURE__*/ _jsx(Card, {
|
|
41
|
+
title: SURFACE_COPY.navGoals,
|
|
42
|
+
titleAs: "h3",
|
|
43
|
+
buttonAriaLabel: SURFACE_COPY.dashShowGoals,
|
|
44
|
+
href: href('/experiments/goals'),
|
|
45
|
+
id: "card-xp-goals"
|
|
46
|
+
})
|
|
47
|
+
})
|
|
48
|
+
]
|
|
49
|
+
})
|
|
50
|
+
]
|
|
51
|
+
});
|
|
52
|
+
}
|
|
@@ -1,30 +1,74 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import './theme.css';
|
|
4
|
+
import { Link, NavGroup, useAuth, useConfig } from '@payloadcms/ui';
|
|
4
5
|
import { formatAdminURL } from 'payload/shared';
|
|
5
6
|
import { usePathname } from 'next/navigation.js';
|
|
6
7
|
import { SURFACE_COPY } from './copy/surface.js';
|
|
7
|
-
|
|
8
|
+
function TestsNav() {
|
|
8
9
|
const { config } = useConfig();
|
|
9
|
-
const
|
|
10
|
+
const { permissions } = useAuth();
|
|
11
|
+
const pathname = usePathname();
|
|
10
12
|
const href = formatAdminURL({
|
|
11
13
|
adminRoute: config.routes.admin,
|
|
12
14
|
path: '/experiments'
|
|
13
15
|
});
|
|
16
|
+
const goalsHref = formatAdminURL({
|
|
17
|
+
adminRoute: config.routes.admin,
|
|
18
|
+
path: '/experiments/goals'
|
|
19
|
+
});
|
|
14
20
|
const base = href.replace(/\/$/, '');
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
21
|
+
const goalsBase = goalsHref.replace(/\/$/, '');
|
|
22
|
+
const goalsActive = pathname === goalsBase || Boolean(pathname?.startsWith(`${goalsBase}/`));
|
|
23
|
+
const testsActive = !goalsActive && (pathname === base || Boolean(pathname?.startsWith(`${base}/`)));
|
|
24
|
+
const testPermissions = permissions?.collections?.['xp-experiments'];
|
|
25
|
+
const canManage = testPermissions?.update === true;
|
|
26
|
+
if (!canManage) return null;
|
|
27
|
+
const entries = [
|
|
28
|
+
{
|
|
29
|
+
id: 'nav-xp-tests',
|
|
30
|
+
href,
|
|
31
|
+
label: SURFACE_COPY.navAllTests,
|
|
32
|
+
active: testsActive
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
id: 'nav-xp-goals',
|
|
36
|
+
href: goalsHref,
|
|
37
|
+
label: SURFACE_COPY.navGoals,
|
|
38
|
+
active: goalsActive
|
|
39
|
+
}
|
|
40
|
+
];
|
|
41
|
+
// getPreference is async and can fetch; let NavGroup default to open.
|
|
42
|
+
return /*#__PURE__*/ _jsx(NavGroup, {
|
|
43
|
+
label: SURFACE_COPY.title,
|
|
44
|
+
children: entries.map((entry)=>{
|
|
45
|
+
const props = {
|
|
46
|
+
className: 'nav__link',
|
|
47
|
+
id: entry.id,
|
|
48
|
+
'aria-current': entry.active ? 'page' : undefined
|
|
49
|
+
};
|
|
50
|
+
const content = /*#__PURE__*/ _jsxs(_Fragment, {
|
|
51
|
+
children: [
|
|
52
|
+
entry.active ? /*#__PURE__*/ _jsx("div", {
|
|
53
|
+
className: "nav__link-indicator"
|
|
54
|
+
}) : null,
|
|
55
|
+
/*#__PURE__*/ _jsx("span", {
|
|
56
|
+
className: "nav__link-label",
|
|
57
|
+
children: entry.label
|
|
58
|
+
})
|
|
59
|
+
]
|
|
60
|
+
});
|
|
61
|
+
return pathname === entry.href ? /*#__PURE__*/ _jsx("div", {
|
|
62
|
+
...props,
|
|
63
|
+
children: content
|
|
64
|
+
}, entry.id) : /*#__PURE__*/ _jsx(Link, {
|
|
65
|
+
...props,
|
|
66
|
+
href: entry.href,
|
|
67
|
+
prefetch: false,
|
|
68
|
+
children: content
|
|
69
|
+
}, entry.id);
|
|
70
|
+
})
|
|
29
71
|
});
|
|
30
72
|
}
|
|
73
|
+
// Preserve the existing import-map key.
|
|
74
|
+
export { TestsNav as TestsNavLink };
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import '../theme.css';
|
|
4
|
+
import { theme } from '../theme.js';
|
|
3
5
|
import { useEffect, useId, useRef, useState } from 'react';
|
|
4
6
|
import { createPortal } from 'react-dom';
|
|
5
7
|
import { Button, TextInput } from '@payloadcms/ui';
|
|
@@ -24,7 +26,7 @@ export function DialogFrame({ title, onClose, children }) {
|
|
|
24
26
|
position: 'fixed',
|
|
25
27
|
inset: 0,
|
|
26
28
|
zIndex: 1000,
|
|
27
|
-
background:
|
|
29
|
+
background: theme.color.overlay,
|
|
28
30
|
display: 'grid',
|
|
29
31
|
placeItems: 'center'
|
|
30
32
|
},
|
|
@@ -35,14 +37,14 @@ export function DialogFrame({ title, onClose, children }) {
|
|
|
35
37
|
"aria-labelledby": heading,
|
|
36
38
|
tabIndex: -1,
|
|
37
39
|
style: {
|
|
38
|
-
background:
|
|
39
|
-
color:
|
|
40
|
-
border:
|
|
40
|
+
background: theme.color.bg,
|
|
41
|
+
color: theme.color.text,
|
|
42
|
+
border: `1px solid ${theme.color.borderStrong}`,
|
|
41
43
|
padding: '2rem',
|
|
42
44
|
width: 'min(36rem, 90vw)',
|
|
43
45
|
maxHeight: '90vh',
|
|
44
46
|
overflow: 'auto',
|
|
45
|
-
borderRadius:
|
|
47
|
+
borderRadius: theme.radius.small
|
|
46
48
|
},
|
|
47
49
|
onKeyDown: (event)=>{
|
|
48
50
|
if (event.defaultPrevented) return;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import '../theme.css';
|
|
2
|
+
import './chipsInput.css';
|
|
3
|
+
export interface ChipsInputProps {
|
|
4
|
+
value: string[];
|
|
5
|
+
onChange: (value: string[]) => void;
|
|
6
|
+
label: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
placeholder?: string;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
id: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function ChipsInput({ value, onChange, label, description, placeholder, disabled, id }: ChipsInputProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useRef, useState } from 'react';
|
|
4
|
+
import { AUDIENCE_COPY } from '../copy/audience.js';
|
|
5
|
+
import '../theme.css';
|
|
6
|
+
import './chipsInput.css';
|
|
7
|
+
export function ChipsInput({ value, onChange, label, description, placeholder, disabled = false, id }) {
|
|
8
|
+
const [draft, setDraft] = useState('');
|
|
9
|
+
const input = useRef(null);
|
|
10
|
+
const commit = (text)=>{
|
|
11
|
+
if (disabled) return;
|
|
12
|
+
const next = [
|
|
13
|
+
...new Set([
|
|
14
|
+
...value,
|
|
15
|
+
...text.split(',')
|
|
16
|
+
].map((item)=>item.trim()).filter(Boolean))
|
|
17
|
+
];
|
|
18
|
+
if (next.length !== value.length || next.some((item, index)=>item !== value[index])) onChange(next);
|
|
19
|
+
setDraft('');
|
|
20
|
+
};
|
|
21
|
+
const remove = (index)=>{
|
|
22
|
+
if (disabled) return;
|
|
23
|
+
onChange(value.filter((_, i)=>i !== index));
|
|
24
|
+
input.current?.focus();
|
|
25
|
+
};
|
|
26
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
27
|
+
className: "xp-chips",
|
|
28
|
+
role: "group",
|
|
29
|
+
"aria-labelledby": `${id}-label`,
|
|
30
|
+
"aria-disabled": disabled,
|
|
31
|
+
children: [
|
|
32
|
+
/*#__PURE__*/ _jsx("label", {
|
|
33
|
+
id: `${id}-label`,
|
|
34
|
+
htmlFor: id,
|
|
35
|
+
children: label
|
|
36
|
+
}),
|
|
37
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
38
|
+
className: "xp-chips-control",
|
|
39
|
+
children: [
|
|
40
|
+
value.map((item, index)=>/*#__PURE__*/ _jsxs("span", {
|
|
41
|
+
className: "xp-chips-item",
|
|
42
|
+
children: [
|
|
43
|
+
/*#__PURE__*/ _jsx("span", {
|
|
44
|
+
children: item
|
|
45
|
+
}),
|
|
46
|
+
/*#__PURE__*/ _jsx("button", {
|
|
47
|
+
type: "button",
|
|
48
|
+
disabled: disabled,
|
|
49
|
+
"aria-label": AUDIENCE_COPY.removeValue(item),
|
|
50
|
+
onClick: ()=>remove(index),
|
|
51
|
+
children: AUDIENCE_COPY.removeSymbol
|
|
52
|
+
})
|
|
53
|
+
]
|
|
54
|
+
}, item)),
|
|
55
|
+
/*#__PURE__*/ _jsx("input", {
|
|
56
|
+
ref: input,
|
|
57
|
+
id: id,
|
|
58
|
+
type: "text",
|
|
59
|
+
value: draft,
|
|
60
|
+
disabled: disabled,
|
|
61
|
+
placeholder: placeholder,
|
|
62
|
+
"aria-describedby": description ? `${id}-description` : undefined,
|
|
63
|
+
onChange: (event)=>setDraft(event.target.value),
|
|
64
|
+
onBlur: ()=>{
|
|
65
|
+
if (draft.trim()) commit(draft);
|
|
66
|
+
},
|
|
67
|
+
onKeyDown: (event)=>{
|
|
68
|
+
if (disabled || event.nativeEvent.isComposing) return;
|
|
69
|
+
if (event.key === 'Enter' || event.key === ',') {
|
|
70
|
+
event.preventDefault();
|
|
71
|
+
commit(draft);
|
|
72
|
+
} else if (event.key === 'Tab' && draft.trim()) commit(draft);
|
|
73
|
+
else if (event.key === 'Backspace' && !draft && value.length) {
|
|
74
|
+
event.preventDefault();
|
|
75
|
+
remove(value.length - 1);
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
onPaste: (event)=>{
|
|
79
|
+
if (disabled) return;
|
|
80
|
+
const pasted = event.clipboardData.getData('text');
|
|
81
|
+
if (!pasted.includes(',')) return;
|
|
82
|
+
event.preventDefault();
|
|
83
|
+
const start = event.currentTarget.selectionStart ?? draft.length;
|
|
84
|
+
const end = event.currentTarget.selectionEnd ?? start;
|
|
85
|
+
commit(draft.slice(0, start) + pasted + draft.slice(end));
|
|
86
|
+
}
|
|
87
|
+
})
|
|
88
|
+
]
|
|
89
|
+
}),
|
|
90
|
+
description ? /*#__PURE__*/ _jsx("p", {
|
|
91
|
+
id: `${id}-description`,
|
|
92
|
+
children: description
|
|
93
|
+
}) : null
|
|
94
|
+
]
|
|
95
|
+
});
|
|
96
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import '../theme.css';
|
|
4
|
+
import { theme } from '../theme.js';
|
|
5
|
+
export function RootViewMessage({ children, alert = false }) {
|
|
6
|
+
return /*#__PURE__*/ _jsx("div", {
|
|
7
|
+
role: alert ? 'alert' : undefined,
|
|
8
|
+
style: {
|
|
9
|
+
padding: theme.space[4],
|
|
10
|
+
color: theme.color.text,
|
|
11
|
+
background: theme.color.bg
|
|
12
|
+
},
|
|
13
|
+
children: children
|
|
14
|
+
});
|
|
15
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
.xp-chips { display: grid; gap: var(--xp-space-2); margin-block: var(--xp-space-4); color: var(--xp-text); font-family: var(--xp-font-body); }
|
|
2
|
+
.xp-chips-control { display: flex; align-items: center; flex-wrap: wrap; gap: var(--xp-space-1); padding: var(--xp-space-2); border: 1px solid var(--xp-border-control); border-radius: var(--xp-radius); background: var(--xp-input-bg); }
|
|
3
|
+
.xp-chips-control:focus-within { outline: 2px solid var(--xp-text); outline-offset: 2px; }
|
|
4
|
+
.xp-chips-item { display: inline-flex; align-items: center; gap: var(--xp-space-1); padding-inline-start: var(--xp-space-2); border-radius: var(--xp-radius); background: var(--xp-surface-raised); overflow-wrap: anywhere; max-width: 100%; }
|
|
5
|
+
.xp-chips .xp-chips-item button { padding: var(--xp-space-1) var(--xp-space-2); border: 0; background: transparent; color: var(--xp-text); font: inherit; cursor: pointer; }
|
|
6
|
+
.xp-chips .xp-chips-control input { flex: 1; min-width: 8rem; width: 0; border: 0; padding: var(--xp-space-1); background: transparent; color: var(--xp-text); font: inherit; }
|
|
7
|
+
.xp-chips input:focus { outline: none; }
|
|
8
|
+
.xp-chips button:focus-visible { outline: 2px solid var(--xp-text); outline-offset: 2px; }
|
|
9
|
+
.xp-chips[aria-disabled='true'] { opacity: .5; }
|
|
10
|
+
.xp-chips[aria-disabled='true'] button { cursor: default; }
|
|
11
|
+
.xp-chips p { margin: 0; color: var(--xp-text-muted); font-size: var(--xp-font-size-small); }
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
export declare const AUDIENCE_COPY: {
|
|
2
|
+
readonly listPlaceholder: "Type a value and press Enter";
|
|
3
|
+
readonly listDescription: "Add one value per chip, for example each page path.";
|
|
4
|
+
readonly removeValue: (value: string) => string;
|
|
5
|
+
readonly removeSymbol: "×";
|
|
2
6
|
readonly emptyListNote: "Add at least one value — an empty list can't be saved.";
|
|
3
7
|
};
|
|
4
8
|
export declare const STRING_OP_LABELS: {
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
export const AUDIENCE_COPY = {
|
|
2
|
+
listPlaceholder: 'Type a value and press Enter',
|
|
3
|
+
listDescription: 'Add one value per chip, for example each page path.',
|
|
4
|
+
removeValue: (value)=>`Remove ${value}`,
|
|
5
|
+
removeSymbol: '×',
|
|
2
6
|
emptyListNote: "Add at least one value — an empty list can't be saved."
|
|
3
7
|
};
|
|
4
8
|
export const STRING_OP_LABELS = {
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export declare const GOALS_COPY: {
|
|
2
|
+
readonly title: "Goals";
|
|
3
|
+
readonly newGoal: "New goal";
|
|
4
|
+
readonly name: "Name";
|
|
5
|
+
readonly source: "Measured by";
|
|
6
|
+
readonly directionColumn: "Better when";
|
|
7
|
+
readonly direction: "Better when it goes";
|
|
8
|
+
readonly increase: "Goes up";
|
|
9
|
+
readonly decrease: "Goes down";
|
|
10
|
+
readonly usedBy: "Used by";
|
|
11
|
+
readonly status: "Status";
|
|
12
|
+
readonly inUse: "In use";
|
|
13
|
+
readonly unused: "Not used";
|
|
14
|
+
readonly empty: "A goal is an outcome you want a test to improve, such as a registration or a gift.";
|
|
15
|
+
readonly loading: "Loading goals…";
|
|
16
|
+
readonly loadError: "Couldn't load goals. Try again.";
|
|
17
|
+
readonly retry: "Try again";
|
|
18
|
+
readonly slug: "Short name";
|
|
19
|
+
readonly slugHelp: "A unique short name for this goal.";
|
|
20
|
+
readonly slugLocked: "The short name stays the same once a test uses this goal.";
|
|
21
|
+
readonly chooseSource: "Choose an outcome";
|
|
22
|
+
readonly window: "Count results for (hours)";
|
|
23
|
+
readonly minimum: "Minimum completions before deciding";
|
|
24
|
+
readonly advanced: "Advanced";
|
|
25
|
+
readonly capping: "Limit very large values";
|
|
26
|
+
readonly capType: "Limit by";
|
|
27
|
+
readonly capValue: "Limit value";
|
|
28
|
+
readonly noCap: "No limit";
|
|
29
|
+
readonly percentile: "Based on past amounts";
|
|
30
|
+
readonly absolute: "Fixed amount";
|
|
31
|
+
readonly capHelp: "For past amounts, enter a number such as 0.99 to limit only the largest 1%; or enter a fixed amount.";
|
|
32
|
+
readonly threshold: "Warn when it gets worse by (%)";
|
|
33
|
+
readonly save: "Save";
|
|
34
|
+
readonly saving: "Saving…";
|
|
35
|
+
readonly cancel: "Cancel";
|
|
36
|
+
readonly remove: "Delete";
|
|
37
|
+
readonly thresholdError: "Enter a percentage of zero or more, or leave blank for 5%.";
|
|
38
|
+
readonly deleting: "Deleting…";
|
|
39
|
+
readonly saveError: "Couldn't save the goal. Try again.";
|
|
40
|
+
readonly deleteError: "Couldn't delete the goal. Try again.";
|
|
41
|
+
readonly breadcrumb: "Breadcrumb";
|
|
42
|
+
readonly missing: "This goal could not be found.";
|
|
43
|
+
};
|
|
44
|
+
export declare function goalUsedBy(count: number): string;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export const GOALS_COPY = {
|
|
2
|
+
title: 'Goals',
|
|
3
|
+
newGoal: 'New goal',
|
|
4
|
+
name: 'Name',
|
|
5
|
+
source: 'Measured by',
|
|
6
|
+
directionColumn: 'Better when',
|
|
7
|
+
direction: 'Better when it goes',
|
|
8
|
+
increase: 'Goes up',
|
|
9
|
+
decrease: 'Goes down',
|
|
10
|
+
usedBy: 'Used by',
|
|
11
|
+
status: 'Status',
|
|
12
|
+
inUse: 'In use',
|
|
13
|
+
unused: 'Not used',
|
|
14
|
+
empty: 'A goal is an outcome you want a test to improve, such as a registration or a gift.',
|
|
15
|
+
loading: 'Loading goals…',
|
|
16
|
+
loadError: "Couldn't load goals. Try again.",
|
|
17
|
+
retry: 'Try again',
|
|
18
|
+
slug: 'Short name',
|
|
19
|
+
slugHelp: 'A unique short name for this goal.',
|
|
20
|
+
slugLocked: 'The short name stays the same once a test uses this goal.',
|
|
21
|
+
chooseSource: 'Choose an outcome',
|
|
22
|
+
window: 'Count results for (hours)',
|
|
23
|
+
minimum: 'Minimum completions before deciding',
|
|
24
|
+
advanced: 'Advanced',
|
|
25
|
+
capping: 'Limit very large values',
|
|
26
|
+
capType: 'Limit by',
|
|
27
|
+
capValue: 'Limit value',
|
|
28
|
+
noCap: 'No limit',
|
|
29
|
+
percentile: 'Based on past amounts',
|
|
30
|
+
absolute: 'Fixed amount',
|
|
31
|
+
capHelp: 'For past amounts, enter a number such as 0.99 to limit only the largest 1%; or enter a fixed amount.',
|
|
32
|
+
threshold: 'Warn when it gets worse by (%)',
|
|
33
|
+
save: 'Save',
|
|
34
|
+
saving: 'Saving…',
|
|
35
|
+
cancel: 'Cancel',
|
|
36
|
+
remove: 'Delete',
|
|
37
|
+
thresholdError: 'Enter a percentage of zero or more, or leave blank for 5%.',
|
|
38
|
+
deleting: 'Deleting…',
|
|
39
|
+
saveError: "Couldn't save the goal. Try again.",
|
|
40
|
+
deleteError: "Couldn't delete the goal. Try again.",
|
|
41
|
+
breadcrumb: 'Breadcrumb',
|
|
42
|
+
missing: 'This goal could not be found.'
|
|
43
|
+
};
|
|
44
|
+
export function goalUsedBy(count) {
|
|
45
|
+
return `Used by ${count} ${count === 1 ? 'test' : 'tests'}`;
|
|
46
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export declare const REVIEW_COPY: {
|
|
2
2
|
readonly noBaseline: "No data yet for this metric.";
|
|
3
3
|
readonly preview: "Preview";
|
|
4
|
+
readonly placedTimes: (n: number) => string;
|
|
4
5
|
readonly noPreview: "Preview isn't set up for this page type";
|
|
5
6
|
readonly raise: "Raise allocation";
|
|
6
7
|
readonly addMetric: "Add a metric";
|
|
@@ -3,6 +3,10 @@ export declare const SURFACE_COPY: {
|
|
|
3
3
|
readonly newTest: "New test";
|
|
4
4
|
readonly goals: "Goals";
|
|
5
5
|
readonly search: "Search tests";
|
|
6
|
+
readonly navAllTests: "All tests";
|
|
7
|
+
readonly navGoals: "Goals";
|
|
8
|
+
readonly dashShowTests: "Show all tests";
|
|
9
|
+
readonly dashShowGoals: "Show all goals";
|
|
6
10
|
readonly backToAdmin: "Back to admin";
|
|
7
11
|
readonly limitedList: "Showing the 200 most recent tests.";
|
|
8
12
|
readonly notInstalled: "The Tests screen isn't installed yet. Run `payload generate:importmap` and reload.";
|
|
@@ -3,6 +3,10 @@ export const SURFACE_COPY = {
|
|
|
3
3
|
newTest: 'New test',
|
|
4
4
|
goals: 'Goals',
|
|
5
5
|
search: 'Search tests',
|
|
6
|
+
navAllTests: 'All tests',
|
|
7
|
+
navGoals: 'Goals',
|
|
8
|
+
dashShowTests: 'Show all tests',
|
|
9
|
+
dashShowGoals: 'Show all goals',
|
|
6
10
|
backToAdmin: 'Back to admin',
|
|
7
11
|
limitedList: 'Showing the 200 most recent tests.',
|
|
8
12
|
notInstalled: "The Tests screen isn't installed yet. Run `payload generate:importmap` and reload.",
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
.xp-dash-group {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
gap: var(--xp-space-4);
|
|
5
|
+
margin-bottom: var(--xp-space-4);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.xp-dash-label { margin: 0; }
|
|
9
|
+
|
|
10
|
+
.xp-dash-card-list {
|
|
11
|
+
padding: 0;
|
|
12
|
+
margin: 0;
|
|
13
|
+
list-style: none;
|
|
14
|
+
display: grid;
|
|
15
|
+
grid-template-columns: repeat(5, 1fr);
|
|
16
|
+
gap: var(--xp-dash-card-gap);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.xp-dash-card-list .card { height: 100%; }
|
|
20
|
+
|
|
21
|
+
@media (max-width: 1440px) {
|
|
22
|
+
.xp-dash-card-list { grid-template-columns: repeat(4, 1fr); }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@media (max-width: 1024px) {
|
|
26
|
+
.xp-dash-card-list { grid-template-columns: repeat(2, 1fr); }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@media (max-width: 768px) {
|
|
30
|
+
.xp-dash-card-list { gap: var(--xp-dash-card-gap-small); }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@media (max-width: 400px) {
|
|
34
|
+
.xp-dash-card-list { grid-template-columns: repeat(1, 1fr); }
|
|
35
|
+
}
|
package/dist/admin/paths.d.ts
CHANGED
|
@@ -6,3 +6,4 @@ export declare const XP_TESTS_LIST_REDIRECT = "@delmaredigital/payload-experimen
|
|
|
6
6
|
export declare const XP_TEST_EDIT_REDIRECT = "@delmaredigital/payload-experiments/admin/rsc#CollectionEditRedirect";
|
|
7
7
|
export declare const XP_TESTS_NAV_LINK = "@delmaredigital/payload-experiments/admin/client#TestsNavLink";
|
|
8
8
|
export declare const XP_TESTS_SURFACE = "@delmaredigital/payload-experiments/admin/client#TestsSurface";
|
|
9
|
+
export declare const XP_TESTS_DASHBOARD_CARDS = "@delmaredigital/payload-experiments/admin/client#TestsDashboardCards";
|
package/dist/admin/paths.js
CHANGED
|
@@ -6,3 +6,4 @@ export const XP_TESTS_LIST_REDIRECT = '@delmaredigital/payload-experiments/admin
|
|
|
6
6
|
export const XP_TEST_EDIT_REDIRECT = '@delmaredigital/payload-experiments/admin/rsc#CollectionEditRedirect';
|
|
7
7
|
export const XP_TESTS_NAV_LINK = '@delmaredigital/payload-experiments/admin/client#TestsNavLink';
|
|
8
8
|
export const XP_TESTS_SURFACE = '@delmaredigital/payload-experiments/admin/client#TestsSurface';
|
|
9
|
+
export const XP_TESTS_DASHBOARD_CARDS = '@delmaredigital/payload-experiments/admin/client#TestsDashboardCards';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { PreflightResult } from '../types/endpoints.js';
|
|
2
|
+
type Placement = PreflightResult['placements'][number];
|
|
3
|
+
type PreviewPage = Pick<Placement, 'collection' | 'id' | 'title' | 'url'> & {
|
|
4
|
+
key: string;
|
|
5
|
+
slotCount: number;
|
|
6
|
+
};
|
|
7
|
+
/** Preview URLs identify pages; counts use the most placements in any one version. */
|
|
8
|
+
export declare function previewPages(placements: readonly Placement[]): PreviewPage[];
|
|
9
|
+
export {};
|