Created
November 18, 2020 21:41
-
-
Save isaac-martin/4c1be17078d148057c75d7ddaf18750a to your computer and use it in GitHub Desktop.
Sanity Custom Input component.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as React from "react"; | |
import { useImmer } from "use-immer"; | |
import Select from "part:@sanity/components/selects/default"; | |
import Fieldset from "part:@sanity/components/fieldsets/default"; | |
import PatchEvent, { setIfMissing, set } from "part:@sanity/form-builder/patch-event"; | |
import { FormBuilderInput } from "part:@sanity/form-builder"; | |
import { FIELD_NAMES } from "./consts"; | |
import Variant from "./variant"; | |
interface State { | |
selectedExperiment: string; | |
experiements: Array<any>; | |
rawOptimizely: Array<any>; | |
variations: Array<any>; | |
dataState: "LOADING" | "SUCCESS" | "ERROR"; | |
} | |
const getExperiments = (rawOptimizely) => { | |
return rawOptimizely.map((experiment) => ({ | |
title: experiment.name, | |
value: experiment.key, | |
})); | |
}; | |
const getExperimentByKey = (experiments, key) => { | |
return experiments.find((exp) => exp.value === key); | |
}; | |
const getVariations = (rawOptimizely, experimentId) => { | |
if (!experimentId) return [] | |
const { variations } = rawOptimizely.find( | |
(exp) => exp.key === experimentId | |
); | |
return variations.map((vari) => ({ | |
title: vari.key, | |
value: vari.key, | |
})); | |
}; | |
const fetchOptimizelyData = () => { | |
const OPTIMIZELY_PROJECT = process.env.SANITY_STUDIO_OPTIMIZELY_PROJECT; | |
const OPTIMIZELY_API_TOKEN = process.env.SANITY_STUDIO_OPTIMIZELY_KEY; | |
return fetch( | |
`https://api.optimizely.com/v2/experiments?project_id=${OPTIMIZELY_PROJECT}`, | |
{ | |
method: "GET", | |
headers: { | |
Authorization: `Bearer ${OPTIMIZELY_API_TOKEN}`, | |
}, | |
redirect: "follow", | |
} | |
) | |
.then((response) => response.json()) | |
.catch((error) => console.error("error", error)); | |
}; | |
const DynamicData: React.FC<any> = React.forwardRef((props, ref) => { | |
const [state, setState] = useImmer<State>({ | |
selectedExperiment: "", | |
experiements: [], | |
rawOptimizely: [], | |
variations: [], | |
dataState: "LOADING", | |
}); | |
const { | |
value: fieldValues, | |
onChange, | |
type, | |
focusPath, | |
onBlur, | |
onFocus, | |
} = props | |
// console.log({ props }) | |
React.useEffect(() => { | |
fetchOptimizelyData().then((res) => { | |
// console.log(res) | |
const experiments = getExperiments(res); | |
const selectedExpID = fieldValues ? fieldValues[FIELD_NAMES.EXPERIMENT_ID] : null; | |
setState((draftState) => { | |
draftState.rawOptimizely = res; | |
draftState.experiements = experiments; | |
draftState.selectedExperiment = getExperimentByKey( | |
experiments, | |
selectedExpID | |
); | |
draftState.variations = getVariations(res, selectedExpID); | |
draftState.dataState = "SUCCESS"; | |
}); | |
}); | |
}, []); | |
// React.useEffect(() => { | |
// console.log('data change') | |
// console.log({ data }) | |
// }, [data]); | |
const handleFieldChange = (field, fieldPatchEvent) => { | |
onChange( | |
fieldPatchEvent | |
.prefixAll(field.name) | |
.prepend(setIfMissing({ _type: type.name })) | |
); | |
}; | |
const handleCustomFieldChange = (field, value, onChange) => { | |
console.log({ field, fieldValues, value }) | |
onChange(PatchEvent.from(set({ ...fieldValues, [field.name]: value, _type: "experiment", _key: `isaac` + Math.random().toString(36).substring(7) }))); | |
}; | |
const handleExperimentIdChange = (field, value, onChange) => { | |
handleCustomFieldChange(field, value, onChange); | |
setState((draftState) => { | |
draftState.selectedExperiment = getExperimentByKey( | |
state.experiements, | |
value | |
); | |
draftState.variations = getVariations(state.rawOptimizely, value); | |
}); | |
}; | |
const { title, description, level, fields } = type; | |
// if (state.dataState === "LOADING") return <>Loading</>; | |
// if (state.dataState === "ERROR") return <>Error, please refresh</>; | |
const [data, setData] = React.useState(false) | |
return ( | |
<Fieldset level={level} legend={title} description={description}> | |
<Select | |
items={state.experiements} | |
// onChange={handleFieldChange(fields[0], onChange)} | |
onChange={(evt) => handleExperimentIdChange(fields[0], evt.value, onChange)} | |
value={state.selectedExperiment} | |
focusPath={focusPath} | |
onFocus={() => { console.log('onFocus') && onFocus }} | |
onBlur={onBlur} | |
ref={ref} | |
/> | |
{fields | |
.filter((field) => field.name !== FIELD_NAMES.EXPERIMENT_ID) | |
.map((field, i) => { | |
if (field.name === FIELD_NAMES.VARIANTS) { | |
return ( | |
<Variant | |
onChange={onChange} | |
variations={state.variations} | |
level={level} | |
field={field} | |
value={fieldValues} | |
focusPath={focusPath} | |
onFocus={onFocus} | |
onBlur={onBlur} | |
type={field.type} | |
// ref={ref} | |
/> | |
); | |
} | |
if (field.name !== FIELD_NAMES.VARIANTS) { | |
// Delegate to the generic FormBuilderInput. | |
// It will resolve and insert the actual input component specified in the schema | |
// Currently this isnt necessary but its a nice to ahve if someone wants to add another field to the experiment objec | |
return ( | |
<FormBuilderInput | |
level={level + 1} | |
key={field.name} | |
type={field.type} | |
// ref={ref} | |
value={fieldValues && fieldValues[field.name]} | |
onChange={(patchEvent) => handleFieldChange(field, patchEvent)} | |
path={[field.name]} | |
focusPath={focusPath} | |
onFocus={onFocus} | |
onBlur={onBlur} | |
/> | |
); | |
} | |
})} | |
</Fieldset> | |
); | |
}); | |
export default DynamicData; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import ExperimentSelector from "../../../src/CustomInputComponents/Experiment"; | |
import { FIELD_NAMES } from "../../../src/CustomInputComponents/Experiment/consts"; | |
import { ObjectType } from "../../../src/types"; | |
const experiment: ObjectType = { | |
name: "experiment", | |
type: "object", | |
title: "Experiment", | |
inputComponent: ExperimentSelector, | |
fields: [ | |
{ | |
name: FIELD_NAMES.EXPERIMENT_ID, | |
type: "string", | |
title: "Experiment ID", | |
}, | |
{ | |
name: FIELD_NAMES.VARIANTS, | |
type: "array", | |
of: [{ type: "variant" }], | |
validation: (Rule) => Rule.required().length(2), // This 2 will be dynamically overwritten based on the amount of options | |
} | |
], | |
}; | |
const variant: ObjectType = { | |
name: "variant", | |
type: "object", | |
title: "Variant", | |
fields: [ | |
{ | |
name: "variantLabel", | |
type: "string", | |
title: "Variant Label", | |
description: "Not shown on frontend", | |
validation: (Rule) => Rule.required() | |
}, | |
{ | |
name: FIELD_NAMES.VARIANT_ID, | |
type: "string", | |
title: "Variant ID", | |
options: { | |
layout: 'dropdown' | |
}, | |
validation: Rule => Rule.custom((field, context) => { | |
const options = context.type.options.list ? context.type.options.list.map(option => option.title) : null | |
console.log({ options, context }) | |
if (!field || !options) return "VariantID is required - please choose from dropdown" | |
if (options.includes(field)) return true | |
return `VariantID must be selected from dropdown - current value for ${context.parent.variantLabel} is "${field}" - did you perhaps change experiment ID?` | |
}) | |
}, | |
{ | |
type: "corePageBuilder", | |
name: "corePageBuilder", | |
description: "Variant Modules" | |
}, | |
], | |
preview: { | |
select: { | |
title: "variantLabel", | |
id: FIELD_NAMES.VARIANT_ID, | |
}, | |
prepare({ title, id }) { | |
return { | |
title, | |
subtitle: `Variant ID: ${id}` | |
}; | |
}, | |
}, | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as React from "react"; | |
import { setIfMissing } from "part:@sanity/form-builder/patch-event"; | |
import { FormBuilderInput } from "part:@sanity/form-builder"; | |
const Variant: React.FC<any> = React.forwardRef((props, ref) => { | |
const { variations, focusPath, level, value, onFocus, onBlur, onChange, type, field: oldField } = props | |
const [field, setField] = React.useState(oldField) | |
const handleFieldChange = (field, fieldPatchEvent) => { | |
onChange( | |
fieldPatchEvent | |
.prefixAll(field.name) | |
.prepend(setIfMissing({ _type: type.name })) | |
); | |
}; | |
React.useEffect(() => { | |
const fieldData = { ...field } | |
// This is so very ugly, but its better then rebuilding all of the page builder with custom input components | |
const validationArr = fieldData.type.validation[0]._rules | |
const validationLength = validationArr.findIndex(rule => rule.flag === "length") | |
// console.log(validationArr) | |
fieldData.type.of[0].fields[1].type.options.list = variations // set our dynamic variations to the pick list | |
validationArr[validationLength].constraint = variations.length // reset the validation count to be the amount of options we have | |
setField(fieldData) | |
}, [variations]) | |
return ( | |
<FormBuilderInput | |
level={level + 1} | |
key={field.name} | |
type={field.type} | |
value={value && value[field.name]} | |
onChange={(patchEvent) => handleFieldChange(field, patchEvent)} | |
path={[field.name]} | |
focusPath={focusPath} | |
onFocus={onFocus} | |
onBlur={onBlur} | |
ref={ref} | |
/> | |
); | |
}); | |
export default Variant; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment