@sanity/personalization-plugin 2.4.1 → 2.5.0-field-level-personalization.1
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 +107 -5
- package/dist/_chunks-cjs/fieldExperiments.js +507 -0
- package/dist/_chunks-cjs/fieldExperiments.js.map +1 -0
- package/dist/_chunks-es/fieldExperiments.mjs +511 -0
- package/dist/_chunks-es/fieldExperiments.mjs.map +1 -0
- package/dist/growthbook/index.js +3 -3
- package/dist/growthbook/index.js.map +1 -1
- package/dist/growthbook/index.mjs +1 -1
- package/dist/index.d.mts +33 -12
- package/dist/index.d.ts +33 -12
- package/dist/index.js +158 -277
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +160 -277
- package/dist/index.mjs.map +1 -1
- package/package.json +20 -20
- package/src/components/ArrayItem.tsx +9 -0
- package/src/components/Select.tsx +1 -1
- package/src/components/{Array.tsx → experiment/Array.tsx} +2 -2
- package/src/components/{ExperimentContext.tsx → experiment/Context.tsx} +2 -2
- package/src/components/{ExperimentField.tsx → experiment/Field.tsx} +11 -8
- package/src/components/{ExperimentInput.tsx → experiment/Input.tsx} +4 -4
- package/src/components/{VariantInput.tsx → experiment/VariantInput.tsx} +2 -1
- package/src/components/{VariantPreview.tsx → experiment/VariantPreview.tsx} +2 -2
- package/src/components/experiment/index.ts +6 -0
- package/src/components/personalization/Array.tsx +59 -0
- package/src/components/personalization/Context.tsx +61 -0
- package/src/components/personalization/Field.tsx +134 -0
- package/src/components/personalization/SegmentInput.tsx +19 -0
- package/src/components/personalization/SegmentPreview.tsx +71 -0
- package/src/components/personalization/index.ts +5 -0
- package/src/fieldExperiments.tsx +44 -12
- package/src/fieldPersonalization.tsx +254 -0
- package/src/index.ts +1 -0
- package/src/types.ts +20 -2
- package/src/utils/clearChildGroups.ts +33 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanity/personalization-plugin",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.0-field-level-personalization.1",
|
|
4
4
|
"description": "Plugin to help with personalization, a/b testing when using Sanity",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sanity",
|
|
@@ -49,39 +49,39 @@
|
|
|
49
49
|
"prepare": "husky"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@sanity/incompatible-plugin": "^1.0.
|
|
53
|
-
"@sanity/studio-secrets": "^3.0.
|
|
54
|
-
"@sanity/ui": "^2.
|
|
52
|
+
"@sanity/incompatible-plugin": "^1.0.5",
|
|
53
|
+
"@sanity/studio-secrets": "^3.0.2",
|
|
54
|
+
"@sanity/ui": "^2.16.12",
|
|
55
55
|
"@sanity/uuid": "^3.0.2",
|
|
56
56
|
"fast-deep-equal": "^3.1.3",
|
|
57
|
-
"react-icons": "^5.
|
|
57
|
+
"react-icons": "^5.5.0",
|
|
58
58
|
"suspend-react": "^0.1.3"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
|
-
"@commitlint/cli": "^19.
|
|
62
|
-
"@commitlint/config-conventional": "^19.
|
|
61
|
+
"@commitlint/cli": "^19.8.1",
|
|
62
|
+
"@commitlint/config-conventional": "^19.8.1",
|
|
63
63
|
"@sanity/pkg-utils": "^6.13.4",
|
|
64
64
|
"@sanity/plugin-kit": "^4.0.19",
|
|
65
65
|
"@sanity/semantic-release-preset": "^5.0.0",
|
|
66
|
-
"@types/react": "^18.3.
|
|
67
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
68
|
-
"@typescript-eslint/parser": "^8.
|
|
66
|
+
"@types/react": "^18.3.23",
|
|
67
|
+
"@typescript-eslint/eslint-plugin": "^8.39.1",
|
|
68
|
+
"@typescript-eslint/parser": "^8.39.1",
|
|
69
69
|
"eslint": "^8.57.1",
|
|
70
|
-
"eslint-config-prettier": "^9.1.
|
|
70
|
+
"eslint-config-prettier": "^9.1.2",
|
|
71
71
|
"eslint-config-sanity": "^7.1.4",
|
|
72
|
-
"eslint-plugin-prettier": "^5.
|
|
73
|
-
"eslint-plugin-react": "^7.37.
|
|
74
|
-
"eslint-plugin-react-hooks": "^5.
|
|
72
|
+
"eslint-plugin-prettier": "^5.5.4",
|
|
73
|
+
"eslint-plugin-react": "^7.37.5",
|
|
74
|
+
"eslint-plugin-react-hooks": "^5.2.0",
|
|
75
75
|
"husky": "^9.1.7",
|
|
76
76
|
"lint-staged": "^15.2.10",
|
|
77
|
-
"prettier": "^3.
|
|
78
|
-
"prettier-plugin-packagejson": "^2.5.
|
|
77
|
+
"prettier": "^3.6.2",
|
|
78
|
+
"prettier-plugin-packagejson": "^2.5.19",
|
|
79
79
|
"react": "^18.3.1",
|
|
80
80
|
"react-dom": "^18.3.1",
|
|
81
|
-
"sanity": "^3.
|
|
82
|
-
"semantic-release": "^24.2.
|
|
83
|
-
"styled-components": "^6.1.
|
|
84
|
-
"typescript": "^5.
|
|
81
|
+
"sanity": "^3.99.0",
|
|
82
|
+
"semantic-release": "^24.2.7",
|
|
83
|
+
"styled-components": "^6.1.19",
|
|
84
|
+
"typescript": "^5.9.2"
|
|
85
85
|
},
|
|
86
86
|
"peerDependencies": {
|
|
87
87
|
"react": "^18 || ^19",
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import {ObjectItem, ObjectItemProps, set} from 'sanity'
|
|
2
|
+
|
|
3
|
+
export const ArrayItem = (props: ObjectItemProps) => {
|
|
4
|
+
const {active} = props.value as ObjectItem & {active: boolean}
|
|
5
|
+
if (!active) {
|
|
6
|
+
props.inputProps.onChange(set(true, ['active']))
|
|
7
|
+
}
|
|
8
|
+
return props.renderDefault(props)
|
|
9
|
+
}
|
|
@@ -2,7 +2,7 @@ import {Select as SanitySelect} from '@sanity/ui'
|
|
|
2
2
|
import {FormEvent} from 'react'
|
|
3
3
|
import {FormPatch, PatchEvent, Path, StringInputProps} from 'sanity'
|
|
4
4
|
|
|
5
|
-
import {SelectOption} from './
|
|
5
|
+
import {SelectOption} from './experiment/Input'
|
|
6
6
|
|
|
7
7
|
export const Select = (
|
|
8
8
|
props: StringInputProps & {
|
|
@@ -3,8 +3,8 @@ import {uuid} from '@sanity/uuid'
|
|
|
3
3
|
import {useCallback} from 'react'
|
|
4
4
|
import {useFormValue} from 'sanity'
|
|
5
5
|
|
|
6
|
-
import {ArrayInputProps, VariantType} from '
|
|
7
|
-
import {useExperimentContext} from './
|
|
6
|
+
import {ArrayInputProps, VariantType} from '../../types'
|
|
7
|
+
import {useExperimentContext} from './Context'
|
|
8
8
|
|
|
9
9
|
export const ArrayInput = (props: ArrayInputProps) => {
|
|
10
10
|
const fieldPath = props.path.slice(0, -1)
|
|
@@ -3,7 +3,7 @@ import {createContext, useContext, useMemo} from 'react'
|
|
|
3
3
|
import {type ObjectInputProps, useClient, useWorkspace} from 'sanity'
|
|
4
4
|
import {suspend} from 'suspend-react'
|
|
5
5
|
|
|
6
|
-
import {ExperimentContextProps,
|
|
6
|
+
import {ExperimentContextProps, ExperimentFieldPluginConfig} from '../../types'
|
|
7
7
|
|
|
8
8
|
// This provider makes the plugin config available to all components in the document form
|
|
9
9
|
// But with experiments resolved
|
|
@@ -28,7 +28,7 @@ export function useExperimentContext() {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
type ExperimentProps = ObjectInputProps & {
|
|
31
|
-
experimentFieldPluginConfig: Required<
|
|
31
|
+
experimentFieldPluginConfig: Required<ExperimentFieldPluginConfig>
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
export function ExperimentProvider(props: ExperimentProps) {
|
|
@@ -11,6 +11,8 @@ import {
|
|
|
11
11
|
set,
|
|
12
12
|
unset,
|
|
13
13
|
} from 'sanity'
|
|
14
|
+
|
|
15
|
+
import {clearChildrenGroups} from '../../utils/clearChildGroups'
|
|
14
16
|
type PatchStuff = {onChange: (patch: FormPatch | FormPatch[] | PatchEvent) => void; inputId: string}
|
|
15
17
|
|
|
16
18
|
const useAddExperimentAction = (
|
|
@@ -99,7 +101,7 @@ const createActions = ({
|
|
|
99
101
|
return active ? removeAction : addAction
|
|
100
102
|
}
|
|
101
103
|
|
|
102
|
-
export const
|
|
104
|
+
export const Field = (
|
|
103
105
|
props: ObjectFieldProps & {
|
|
104
106
|
experimentNameOverride: string
|
|
105
107
|
experimentId: string
|
|
@@ -127,12 +129,13 @@ export const ExperimentField = (
|
|
|
127
129
|
return [createActions(actionProps), ...oldActions]
|
|
128
130
|
}, [actionProps, props.actions])
|
|
129
131
|
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
132
|
+
const enhancedProps = useMemo(() => {
|
|
133
|
+
const propsWithClearedGroups = clearChildrenGroups(props)
|
|
134
|
+
return {
|
|
135
|
+
...propsWithClearedGroups,
|
|
133
136
|
actions: memoizedActions,
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
return props.renderDefault(
|
|
137
|
+
}
|
|
138
|
+
}, [props, memoizedActions])
|
|
139
|
+
|
|
140
|
+
return props.renderDefault(enhancedProps)
|
|
138
141
|
}
|
|
@@ -11,9 +11,9 @@ import {
|
|
|
11
11
|
useFormValue,
|
|
12
12
|
} from 'sanity'
|
|
13
13
|
|
|
14
|
-
import {ExperimentType} from '
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
14
|
+
import {ExperimentType} from '../..'
|
|
15
|
+
import {Select} from '../Select'
|
|
16
|
+
import {useExperimentContext} from './Context'
|
|
17
17
|
|
|
18
18
|
export type SelectOption = {title: string; value: string}
|
|
19
19
|
const formatlistOptions = (experiments: ExperimentType[]): SelectOption[] =>
|
|
@@ -22,7 +22,7 @@ const formatlistOptions = (experiments: ExperimentType[]): SelectOption[] =>
|
|
|
22
22
|
value: experiment.id,
|
|
23
23
|
}))
|
|
24
24
|
|
|
25
|
-
export const
|
|
25
|
+
export const Input = (
|
|
26
26
|
props: StringInputProps & {variantNameOverride: string; experimentNameOverride: string},
|
|
27
27
|
) => {
|
|
28
28
|
const {experiments} = useExperimentContext()
|
|
@@ -2,7 +2,8 @@ import {Button, Inline, Stack} from '@sanity/ui'
|
|
|
2
2
|
import {ObjectInputProps, set, useFormValue} from 'sanity'
|
|
3
3
|
|
|
4
4
|
export const VariantInput = (props: ObjectInputProps) => {
|
|
5
|
-
const
|
|
5
|
+
const experimentPath = props.path.slice(0, -2)
|
|
6
|
+
const defaultValue = useFormValue([...experimentPath, 'default'])
|
|
6
7
|
const handleClick = () => {
|
|
7
8
|
props.onChange(set(defaultValue, ['value']))
|
|
8
9
|
}
|
|
@@ -8,8 +8,8 @@ import {
|
|
|
8
8
|
useClient,
|
|
9
9
|
} from 'sanity'
|
|
10
10
|
|
|
11
|
-
import {VariantPreviewProps} from '
|
|
12
|
-
import {useExperimentContext} from './
|
|
11
|
+
import {VariantPreviewProps} from '../../types'
|
|
12
|
+
import {useExperimentContext} from './Context'
|
|
13
13
|
|
|
14
14
|
export const VariantPreview = (props: PreviewProps) => {
|
|
15
15
|
const [subtitle, setSubtitle] = useState<string | undefined>(undefined)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import {Button, Inline, Stack} from '@sanity/ui'
|
|
2
|
+
import {uuid} from '@sanity/uuid'
|
|
3
|
+
import {useCallback} from 'react'
|
|
4
|
+
|
|
5
|
+
import {PersonalizationArrayInputProps, VariantType} from '../../types'
|
|
6
|
+
import {usePersonalizationContext} from './Context'
|
|
7
|
+
|
|
8
|
+
export const ArrayInput = (props: PersonalizationArrayInputProps) => {
|
|
9
|
+
const {onItemAppend, segmentName, segmentId} = props
|
|
10
|
+
|
|
11
|
+
const {segments} = usePersonalizationContext()
|
|
12
|
+
|
|
13
|
+
const handleClick = useCallback(
|
|
14
|
+
async (segment: VariantType) => {
|
|
15
|
+
const item = {
|
|
16
|
+
_key: uuid(),
|
|
17
|
+
[segmentId]: segment.id,
|
|
18
|
+
_type: segmentName,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Patch the document
|
|
22
|
+
onItemAppend(item)
|
|
23
|
+
},
|
|
24
|
+
[segmentId, segmentName, onItemAppend],
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
type Value = {
|
|
28
|
+
value?: unknown
|
|
29
|
+
[key: string]: string | unknown
|
|
30
|
+
segmentId: string
|
|
31
|
+
_key: string
|
|
32
|
+
_type: string
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// there is probably some better was of getting the type of this?
|
|
36
|
+
const values = (props.value as Value[]) || []
|
|
37
|
+
|
|
38
|
+
const usedSegments = values?.map((segment) => segment[segmentId])
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<Stack space={3}>
|
|
42
|
+
{props.renderDefault({...props, arrayFunctions: () => null})}
|
|
43
|
+
|
|
44
|
+
<Inline space={1}>
|
|
45
|
+
{segments.map((segment) => {
|
|
46
|
+
return (
|
|
47
|
+
<Button
|
|
48
|
+
key={`${segment.id}`}
|
|
49
|
+
text={`Add ${segment.label}`}
|
|
50
|
+
mode="ghost"
|
|
51
|
+
disabled={usedSegments?.includes(segment.id)}
|
|
52
|
+
onClick={() => handleClick(segment)}
|
|
53
|
+
/>
|
|
54
|
+
)
|
|
55
|
+
})}
|
|
56
|
+
</Inline>
|
|
57
|
+
</Stack>
|
|
58
|
+
)
|
|
59
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import equal from 'fast-deep-equal'
|
|
2
|
+
import {createContext, useContext, useMemo} from 'react'
|
|
3
|
+
import {ObjectInputProps, useClient, useWorkspace} from 'sanity'
|
|
4
|
+
import {suspend} from 'suspend-react'
|
|
5
|
+
|
|
6
|
+
import {PersonalizationContextProps, PersonalizationFieldPluginConfig} from '../../types'
|
|
7
|
+
|
|
8
|
+
export const CONFIG_DEFAULT = {
|
|
9
|
+
fields: [],
|
|
10
|
+
apiVersion: '2024-11-07',
|
|
11
|
+
personalizationNameOverride: 'personalization',
|
|
12
|
+
segmentNameOverride: 'segment',
|
|
13
|
+
segmentId: 'segmentId',
|
|
14
|
+
segmentArrayName: 'segments',
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const PersonalizationContext = createContext<PersonalizationContextProps>({
|
|
18
|
+
...CONFIG_DEFAULT,
|
|
19
|
+
segments: [],
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
export function usePersonalizationContext() {
|
|
23
|
+
return useContext(PersonalizationContext)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
type PersonalizationProps = ObjectInputProps & {
|
|
27
|
+
personalizationFieldPluginConfig: Required<PersonalizationFieldPluginConfig>
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function PersonalizationProvider(props: PersonalizationProps) {
|
|
31
|
+
const {personalizationFieldPluginConfig} = props
|
|
32
|
+
|
|
33
|
+
const client = useClient({apiVersion: personalizationFieldPluginConfig.apiVersion})
|
|
34
|
+
const workspace = useWorkspace()
|
|
35
|
+
|
|
36
|
+
// Fetch or return experiments
|
|
37
|
+
const segments = Array.isArray(personalizationFieldPluginConfig.segments)
|
|
38
|
+
? personalizationFieldPluginConfig.segments
|
|
39
|
+
: suspend(
|
|
40
|
+
// eslint-disable-next-line require-await
|
|
41
|
+
async () => {
|
|
42
|
+
if (typeof personalizationFieldPluginConfig.segments === 'function') {
|
|
43
|
+
return personalizationFieldPluginConfig.segments(client)
|
|
44
|
+
}
|
|
45
|
+
return personalizationFieldPluginConfig.segments
|
|
46
|
+
},
|
|
47
|
+
[workspace],
|
|
48
|
+
{equal},
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
const context = useMemo(
|
|
52
|
+
() => ({...personalizationFieldPluginConfig, segments}),
|
|
53
|
+
[personalizationFieldPluginConfig, segments],
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<PersonalizationContext.Provider value={context}>
|
|
58
|
+
{props.renderDefault(props)}
|
|
59
|
+
</PersonalizationContext.Provider>
|
|
60
|
+
)
|
|
61
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import {CloseIcon} from '@sanity/icons'
|
|
2
|
+
import React, {useCallback, useMemo} from 'react'
|
|
3
|
+
import {IoMdPeople} from 'react-icons/io'
|
|
4
|
+
import {
|
|
5
|
+
defineDocumentFieldAction,
|
|
6
|
+
DocumentFieldActionItem,
|
|
7
|
+
DocumentFieldActionProps,
|
|
8
|
+
FormPatch,
|
|
9
|
+
ObjectFieldProps,
|
|
10
|
+
PatchEvent,
|
|
11
|
+
set,
|
|
12
|
+
unset,
|
|
13
|
+
} from 'sanity'
|
|
14
|
+
|
|
15
|
+
import {clearChildrenGroups} from '../../utils/clearChildGroups'
|
|
16
|
+
|
|
17
|
+
type PatchStuff = {onChange: (patch: FormPatch | FormPatch[] | PatchEvent) => void; inputId: string}
|
|
18
|
+
|
|
19
|
+
const useAddExperimentAction = (
|
|
20
|
+
props: DocumentFieldActionProps &
|
|
21
|
+
PatchStuff & {personalizationNameOverride: string; active: boolean},
|
|
22
|
+
): DocumentFieldActionItem => {
|
|
23
|
+
const {onChange, active, personalizationNameOverride} = props
|
|
24
|
+
|
|
25
|
+
const handleAddAction = useCallback(() => {
|
|
26
|
+
onChange([set(!active, ['active'])])
|
|
27
|
+
}, [onChange, active])
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
title: `Add ${personalizationNameOverride}`,
|
|
31
|
+
type: 'action',
|
|
32
|
+
icon: IoMdPeople,
|
|
33
|
+
onAction: handleAddAction,
|
|
34
|
+
renderAsButton: true,
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const useRemoveExperimentAction = (
|
|
39
|
+
props: DocumentFieldActionProps &
|
|
40
|
+
PatchStuff & {
|
|
41
|
+
personalizationNameOverride: string
|
|
42
|
+
active: boolean
|
|
43
|
+
segmentNameOverride: string
|
|
44
|
+
},
|
|
45
|
+
): DocumentFieldActionItem => {
|
|
46
|
+
const {onChange, active, personalizationNameOverride, segmentNameOverride} = props
|
|
47
|
+
const handleClearAction = useCallback(() => {
|
|
48
|
+
const activeId = ['active']
|
|
49
|
+
const segments = [`${segmentNameOverride}s`]
|
|
50
|
+
onChange([set(!active, activeId), unset(segments)])
|
|
51
|
+
}, [onChange, active, segmentNameOverride])
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
title: `Remove ${personalizationNameOverride}`,
|
|
55
|
+
type: 'action',
|
|
56
|
+
icon: CloseIcon,
|
|
57
|
+
onAction: handleClearAction,
|
|
58
|
+
renderAsButton: true,
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const createActions = ({
|
|
63
|
+
onChange,
|
|
64
|
+
inputId,
|
|
65
|
+
active,
|
|
66
|
+
personalizationNameOverride,
|
|
67
|
+
segmentNameOverride,
|
|
68
|
+
}: PatchStuff & {
|
|
69
|
+
active?: boolean
|
|
70
|
+
personalizationNameOverride: string
|
|
71
|
+
segmentNameOverride: string
|
|
72
|
+
}) => {
|
|
73
|
+
const removeAction = defineDocumentFieldAction({
|
|
74
|
+
name: `Remove ${personalizationNameOverride}`,
|
|
75
|
+
useAction: (props) =>
|
|
76
|
+
useRemoveExperimentAction({
|
|
77
|
+
...props,
|
|
78
|
+
active: true,
|
|
79
|
+
onChange,
|
|
80
|
+
inputId,
|
|
81
|
+
personalizationNameOverride,
|
|
82
|
+
segmentNameOverride,
|
|
83
|
+
}),
|
|
84
|
+
})
|
|
85
|
+
const addAction = defineDocumentFieldAction({
|
|
86
|
+
name: `Add ${personalizationNameOverride}`,
|
|
87
|
+
useAction: (props) =>
|
|
88
|
+
useAddExperimentAction({
|
|
89
|
+
...props,
|
|
90
|
+
active: false,
|
|
91
|
+
onChange,
|
|
92
|
+
inputId,
|
|
93
|
+
personalizationNameOverride,
|
|
94
|
+
}),
|
|
95
|
+
})
|
|
96
|
+
return active ? removeAction : addAction
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export const Field = (
|
|
100
|
+
props: ObjectFieldProps & {
|
|
101
|
+
personalizationNameOverride: string
|
|
102
|
+
segmentNameOverride: string
|
|
103
|
+
},
|
|
104
|
+
): React.ReactElement => {
|
|
105
|
+
const {onChange} = props.inputProps
|
|
106
|
+
const {inputId, personalizationNameOverride, segmentNameOverride} = props
|
|
107
|
+
const active = props.value?.active as boolean | undefined
|
|
108
|
+
|
|
109
|
+
const actionProps = useMemo(
|
|
110
|
+
() => ({
|
|
111
|
+
onChange,
|
|
112
|
+
inputId,
|
|
113
|
+
active,
|
|
114
|
+
personalizationNameOverride,
|
|
115
|
+
segmentNameOverride,
|
|
116
|
+
}),
|
|
117
|
+
[onChange, inputId, active, personalizationNameOverride, segmentNameOverride],
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
const memoizedActions = useMemo(() => {
|
|
121
|
+
const oldActions = props.actions || []
|
|
122
|
+
return [createActions(actionProps), ...oldActions]
|
|
123
|
+
}, [actionProps, props.actions])
|
|
124
|
+
|
|
125
|
+
const enhancedProps = useMemo(() => {
|
|
126
|
+
const propsWithClearedGroups = clearChildrenGroups(props)
|
|
127
|
+
return {
|
|
128
|
+
...propsWithClearedGroups,
|
|
129
|
+
actions: memoizedActions,
|
|
130
|
+
}
|
|
131
|
+
}, [props, memoizedActions])
|
|
132
|
+
|
|
133
|
+
return props.renderDefault(enhancedProps)
|
|
134
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import {Button, Inline, Stack} from '@sanity/ui'
|
|
2
|
+
import {ObjectInputProps, set, useFormValue} from 'sanity'
|
|
3
|
+
|
|
4
|
+
export const SegmentInput = (props: ObjectInputProps) => {
|
|
5
|
+
const personalizationPath = props.path.slice(0, -2)
|
|
6
|
+
const defaultValue = useFormValue([...personalizationPath, 'default'])
|
|
7
|
+
const handleClick = () => {
|
|
8
|
+
props.onChange(set(defaultValue, ['value']))
|
|
9
|
+
}
|
|
10
|
+
return (
|
|
11
|
+
<Stack space={3}>
|
|
12
|
+
{props.renderDefault(props)}
|
|
13
|
+
|
|
14
|
+
<Inline space={1}>
|
|
15
|
+
<Button text="Copy default" mode="ghost" onClick={() => handleClick()} />
|
|
16
|
+
</Inline>
|
|
17
|
+
</Stack>
|
|
18
|
+
)
|
|
19
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import {useEffect, useState} from 'react'
|
|
2
|
+
import {
|
|
3
|
+
isImage,
|
|
4
|
+
isReference,
|
|
5
|
+
ObjectSchemaType,
|
|
6
|
+
PreviewProps,
|
|
7
|
+
ReferenceSchemaType,
|
|
8
|
+
useClient,
|
|
9
|
+
} from 'sanity'
|
|
10
|
+
|
|
11
|
+
import {VariantPreviewProps} from '../../types'
|
|
12
|
+
import {usePersonalizationContext} from './Context'
|
|
13
|
+
|
|
14
|
+
export const SegmentPreview = (props: PreviewProps) => {
|
|
15
|
+
const [subtitle, setSubtitle] = useState<string | undefined>(undefined)
|
|
16
|
+
const [title, setTitle] = useState<string | undefined>(undefined)
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
18
|
+
const [media, setMedia] = useState<any>(undefined)
|
|
19
|
+
const client = useClient({apiVersion: '2025-01-01'})
|
|
20
|
+
const {segments} = usePersonalizationContext()
|
|
21
|
+
|
|
22
|
+
const {segment, value} = props as VariantPreviewProps
|
|
23
|
+
|
|
24
|
+
const selectedSegment = segments.find((segmentItem) => {
|
|
25
|
+
return segmentItem.id === segment
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
const getSubtitle = async () => {
|
|
30
|
+
setTitle(`${selectedSegment?.label}`)
|
|
31
|
+
if (typeof value === 'string') {
|
|
32
|
+
return setSubtitle(value)
|
|
33
|
+
}
|
|
34
|
+
if (isReference(value)) {
|
|
35
|
+
const doc = await client.getDocument(value._ref)
|
|
36
|
+
const type = props.schemaType as ObjectSchemaType
|
|
37
|
+
const valueField = type.fields.find((field) => field.name === 'value') as ObjectSchemaType
|
|
38
|
+
const referenceField = valueField?.type as ReferenceSchemaType
|
|
39
|
+
const referenceType = referenceField.to.find((field) => field.type?.name === doc?._type)
|
|
40
|
+
|
|
41
|
+
const selectFields = {} as Record<string, unknown>
|
|
42
|
+
const previewFields = referenceType?.preview?.select || {}
|
|
43
|
+
Object.keys(previewFields).forEach((key) => {
|
|
44
|
+
const valueKey = referenceType?.preview?.select?.[key]
|
|
45
|
+
selectFields[key] =
|
|
46
|
+
valueKey && doc
|
|
47
|
+
? valueKey?.split('.').reduce((acc, index) => acc[index], doc)
|
|
48
|
+
: undefined
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
const previewContent = referenceType?.preview?.prepare?.(selectFields)
|
|
52
|
+
setMedia(previewContent?.media || selectFields.media)
|
|
53
|
+
return setSubtitle(previewContent?.title || (selectFields?.title as string))
|
|
54
|
+
}
|
|
55
|
+
if (isImage(value)) {
|
|
56
|
+
setMedia(value)
|
|
57
|
+
}
|
|
58
|
+
return ''
|
|
59
|
+
}
|
|
60
|
+
getSubtitle()
|
|
61
|
+
}, [value, client, selectedSegment?.label, props.schemaType])
|
|
62
|
+
|
|
63
|
+
const previewProps = {
|
|
64
|
+
...props,
|
|
65
|
+
title: title,
|
|
66
|
+
subtitle: subtitle,
|
|
67
|
+
media: media,
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return props.renderDefault(previewProps)
|
|
71
|
+
}
|