Skip to content

Instantly share code, notes, and snippets.

@kevinfoerster
Last active May 1, 2020 15:57
Show Gist options
  • Save kevinfoerster/fcbca3fd088453ec7dfb522075491ed0 to your computer and use it in GitHub Desktop.
Save kevinfoerster/fcbca3fd088453ec7dfb522075491ed0 to your computer and use it in GitHub Desktop.
Generated by XState Viz: https://xstate.js.org/viz
// has articles, apple only
// const endpoint = 'https://www.mocky.io/v2/5e84a71e300000a73fcf4653';
// has articles, with samsung and apple
// const endpoint = 'https://www.mocky.io/v2/5e84dc35300000460a97ac9b';
// const endpoint = 'https://www.mocky.io/v2/5e9f5d792d00005300cb7b63';
// const coveragesEndpoint = 'https://www.mocky.io/v2/5e9f5d792d00005300cb7b63'
const endpoint = {
articles: 'https://www.mocky.io/v2/5ea024ca320000540094ac0c',
coverages: 'https://www.mocky.io/v2/5e9f5d792d00005300cb7b63'
};
console.log('should contain requesting ...')
const ApiService = endpoint => {
console.log('requesting ...');
return fetch(endpoint, {
methods: 'GET',
headers: {
'content-type': 'application/json',
},
}).then(response => response.json());
};
const setFilter = (event, filter) => {
const defaultFilter = {
manufacturer: '',
category: '',
series: '',
model: '',
color: '',
damage: '',
};
switch (event.type) {
case 'SELECTMANUFACTURER':
return {
...defaultFilter,
manufacturer: event.value,
};
case 'SELECTCATEGORY':
return {
...defaultFilter,
manufacturer: filter.manufacturer,
category: event.value,
};
case 'SELECTSERIES':
return {
...defaultFilter,
manufacturer: filter.manufacturer,
category: filter.category,
// happens only on "andere serie"
series: event.value === '' ? null : event.value,
};
case 'SELECTMODEL':
return {
manufacturer: filter.manufacturer,
category: filter.category,
series: filter.series,
model: event.value,
};
case 'SELECTDAMAGE':
return {
manufacturer: filter.manufacturer,
category: filter.category,
series: filter.series,
model: filter.model,
damage: event.value,
};
case 'SELECTCOLOR':
return {
manufacturer: filter.manufacturer,
category: filter.category,
series: filter.series,
model: filter.model,
damage: filter.damage,
color: event.value,
};
default:
return defaultFilter;
}
};
const applyFilter = (filter, event, context) => {
const articles = context.allArticles;
const defaultFilteredArticles = {
manufacturer: [],
category: [],
series: [],
model: [],
damage: [],
color: [],
current: [],
};
const filterKeys = Object.keys(filter); // ["manufacturer", "series"...]
const filteredArticles = articles.filter((article) => {
let result = true;
filterKeys.forEach((filterProp) => {
if (filter[filterProp] !== '') {
if (article[filterProp] !== filter[filterProp]) {
result = false;
}
}
});
return result;
});
if (!event || event.type === '') {
return { current: filteredArticles };
}
switch (event.type) {
case 'SELECTMANUFACTURER':
return {
...defaultFilteredArticles,
manufacturer: filteredArticles,
current: filteredArticles,
};
case 'SELECTCATEGORY':
return {
...defaultFilteredArticles,
manufacturer: context.filteredArticles.manufacturer,
category: filteredArticles,
current: filteredArticles,
};
case 'SELECTSERIES':
return {
...defaultFilteredArticles,
manufacturer: context.filteredArticles.manufacturer,
category: context.filteredArticles.category,
series: filteredArticles,
current: filteredArticles,
};
case 'SELECTMODEL':
return {
...defaultFilteredArticles,
manufacturer: context.filteredArticles.manufacturer,
category: context.filteredArticles.category,
series: context.filteredArticles.series,
model: filteredArticles,
current: filteredArticles,
};
case 'SELECTDAMAGE':
return {
...defaultFilteredArticles,
manufacturer: context.filteredArticles.manufacturer,
category: context.filteredArticles.category,
series: context.filteredArticles.series,
model: context.filteredArticles.model,
damage: filteredArticles,
current: filteredArticles,
};
case 'SELECTCOLOR':
return {
...defaultFilteredArticles,
manufacturer: context.filteredArticles.manufacturer,
category: context.filteredArticles.category,
series: context.filteredArticles.series,
model: context.filteredArticles.model,
damage: context.filteredArticles.damage,
color: filteredArticles,
current: filteredArticles,
};
default:
return defaultFilteredArticles;
}
};
const selectCoverageTransitions = [
{
target: 'showArticle',
actions: ['selectCoverageOption', 'hidePrice'],
cond: 'shouldSkipDamages',
},
{
target: 'showDamages',
actions: ['selectCoverageOption', 'hidePrice'],
cond: 'shouldHidePrice',
},
{
target: 'showDamages',
actions: ['selectCoverageOption', 'showPrice'],
// cond: 'shouldShowPrice',
},
];
const selectDamageTransitions = [
{
target: 'showArticle',
actions: ['selectFilter', 'hidePrice'],
cond: 'shouldUseFallbackArticle',
},
{
target: 'showArticle',
actions: ['selectFilter', 'hidePrice'],
cond: 'shouldHidePrice',
},
{
target: 'showArticle',
actions: ['selectFilter', 'showPrice'],
// cond: 'shouldShowPrice'
},
];
const additionalDeviceDetailsStepMachine = {
id: 'additionalDeviceDetailsStep',
initial: 'checkPreconditions',
states: {
checkPreconditions: {
on: {
'': [
// all colors are `null`
{
target: 'showDeviceDetails',
cond: 'shouldOmitColors' // check all colors for all items in current
},
{
// THEFT but no colors
target: 'showDeviceDetails',
cond: 'isTheftAndShouldOmitColor'
},
{
// THEFT
target: 'showColor',
cond: 'isTheft'
},
{
// OTHER DAMAGE
target: 'showColor',
cond: 'isDamageFallback'
},
{
// OTHER DAMAGE
target: 'showDeviceDetails',
cond: 'isDamageFallbackAndShouldOmitColor'
},
// articlelist with at least one valid color
{
target: 'showColor'
}
]
}
},
showColor: {
on: {
'': {
// just one color -> preselect this color
target: 'showDeviceDetails',
actions: 'autoSelectColor',
cond: 'shouldSkipColors' // only one not null color was provided
},
SELECTCOLOR: {
target: 'showDeviceDetails',
actions: ['selectFilter'],
},
}
// target: 'showColor', // TBD
// actions: ['selectFilter'],
},
showDeviceDetails: {
}
}
};
const coverageDamageMachine = {
id: 'coverageDamage',
initial: 'requestCoveragesList',
states: {
requestCoveragesList: {
invoke: {
id: 'ApiService',
src: (_context, _event) => ApiService(endpoint.coverages),
onDone: {
target: 'initWithPayload',
actions: 'setAllCoverages',
},
onError: {
target: 'requestCoveragesListFailed',
actions: (_context, _event) => {
// eslint-disable-next-line no-console
console.error('request failed');
},
},
},
},
noCoverages: {
type: 'final',
},
initWithPayload: {
on: {
'': [
{
target: 'showSelectedDevice',
// actions: ['filterArticles'],
cond: 'hasCoverages',
},
{ target: 'noCoverages' },
],
},
},
requestCoveragesListFailed: {
on: {
RETRY: {
target: 'requestCoveragesList',
},
},
},
showSelectedDevice: {
on: {
EDITDEVICE: {
actions: ['removeModel', send('BACK')],
},
'': {
target: 'showCoverages',
},
},
},
showCoverages: {
on: {
EDITDEVICE: {
actions: ['removeModel', send('BACK')],
},
SELECTCOVERAGE: selectCoverageTransitions,
},
},
showDamages: {
on: {
EDITDEVICE: {
actions: ['removeModel', send('BACK')],
},
SELECTCOVERAGE: selectCoverageTransitions,
SELECTDAMAGE: selectDamageTransitions,
},
},
showArticle: {
on: {
EDITDEVICE: {
actions: ['removeModel', send('BACK')],
},
SELECTCOVERAGE: selectCoverageTransitions,
SELECTDAMAGE: selectDamageTransitions,
},
},
},
};
const articleFilterMachine = {
id: 'articleFilter',
initial: 'showManufacturer',
states: {
showManufacturer: {
on: {
'': {
target: 'showCategory',
actions: ['filterArticles'],
cond: 'manufacturerIsSet',
},
SELECTMANUFACTURER: {
target: 'showCategory',
actions: ['selectFilter'],
},
},
},
showCategory: {
on: {
'': [
{
target: 'showSelectionUnavailable',
cond: 'noMatchingArticles',
},
{
target: 'showSeries',
actions: ['filterArticles'],
cond: 'categoryIsSet',
},
{
target: 'showSeries',
actions: ['autoSetCategory'],
cond: 'shouldSkipCategory',
},
],
SELECTMANUFACTURER: {
target: 'showCategory',
actions: ['selectFilter'],
},
SELECTCATEGORY: {
target: 'showSeries',
actions: ['selectFilter'],
},
},
},
showSeries: {
on: {
'': [
{
target: 'showSelectionUnavailable',
cond: 'noMatchingArticles',
},
{
target: 'showModel',
actions: ['filterArticles'],
cond: 'seriesIsSet',
},
{
target: 'showModel',
cond: 'shouldOmitSeries',
},
],
SELECTMANUFACTURER: {
target: 'showCategory',
actions: ['selectFilter'],
},
SELECTCATEGORY: {
target: 'showSeries',
actions: ['selectFilter'],
},
SELECTSERIES: {
target: 'showModel',
actions: ['selectFilter'],
},
},
},
showModel: {
on: {
'': [
{
target: 'showSelectionUnavailable',
cond: 'noMatchingArticles',
},
{
target: 'allFiltersSelected',
actions: ['filterArticles'],
cond: 'modelIsSet',
},
],
SELECTMODEL: {
target: 'allFiltersSelected',
actions: ['selectFilter'],
},
SELECTMANUFACTURER: {
target: 'showCategory',
actions: ['selectFilter'],
},
SELECTCATEGORY: {
target: 'showSeries',
actions: ['selectFilter'],
},
SELECTSERIES: {
target: 'showModel',
actions: ['selectFilter'],
},
},
},
showSelectionUnavailable: {
type: 'final',
// eslint-disable-next-line no-console
entry: (_context, _event) => console.log('selection unavailable'),
},
allFiltersSelected: {
type: 'final',
// eslint-disable-next-line no-console
// entry: (context, _event) => console.log(context.filteredArticles),
entry: send('NEXT'),
},
},
};
const initMachine = {
id: 'init',
initial: 'requestArticleList',
states: {
requestArticleList: {
invoke: {
id: 'ApiService',
src: (_context, _event) => ApiService(endpoint.articles),
onDone: {
target: 'initWithPayload',
actions: 'setAllArticles',
},
onError: {
target: 'requestArticleListFailed',
actions: (_context, _event) => {
// eslint-disable-next-line no-console
console.error('request failed');
},
},
},
},
noArticles: {
type: 'final',
},
initWithPayload: {
on: {
'': [
{
target: '#form.articleFilterStep',
// actions: ['filterArticles'],
cond: 'hasArticles',
},
{ target: 'noArticles' },
],
},
},
requestArticleListFailed: {
on: {
RETRY: {
target: 'requestArticleList',
},
},
},
},
};
const formMachine = Machine(
{
id: 'form',
initial: 'init',
context: {
allArticles: [],
filteredArticles: {
manufacturer: [],
category: [],
series: [],
model: [],
damage: [],
color: [],
current: [],
},
filter: {
manufacturer: '',
category: '',
series: '',
model: '',
damage: '',
color: ''
},
allCoverages: [],
selectedCoverageOption: {},
isPriceVisible: true,
},
states: {
init: {
...initMachine,
},
articleFilterStep: {
...articleFilterMachine,
on: {
NEXT: 'coverageDamageStep',
},
},
coverageDamageStep: {
...coverageDamageMachine,
on: {
NEXT: 'additionalDeviceDetailsStep',
BACK: 'articleFilterStep',
},
},
additionalDeviceDetailsStep: {
...additionalDeviceDetailsStepMachine,
// eslint-disable-next-line no-console
entry: (_context, _event) => console.log('to be continued, you arrived in additionalDeviceInfoStep'),
on: {
NEXT: 'dontKnow',
BACK: 'coverageDamageStep',
},
},
dontKnow: {
// somehow something leads to something
},
summaryStep: {
type: 'final',
},
},
},
{
guards: {
shouldOmitColors: (context, event) => {
return context.filteredArticles.damage.every(article => article.color === null)
},
isTheft: (context, event) => {
return context.selectedCoverageOption.problem === 'THEFT'
},
isDamageFallback: (context, event) => {
return context.filter.damage === 'OTHER'
},
shouldSkipColors: (context, event) => {
let items = [];
if(context.selectedCoverageOption.problem === 'THEFT'){
items = context.filteredArticles.model
} else {
items = context.filteredArticles.damage
}
const colors = items.filter(item => item.color !== null).map(item => item.color)
return Array.from(new Set(colors)).length === 1
},
isDamageFallbackAndShouldOmitColor: (context, event) => {
const items = context.filteredArticles.model
.filter(item => item.damage === "OTHER")
.filter(item => item.color !== null)
.map(item => item.color);
return Array.from(new Set(items)).length === 0
},
isTheftAndShouldOmitColor: (context, event) => {
const items = context.filteredArticles.model
.filter(item => item.color !== null)
.map(item => item.color);
return Array.from(new Set(items)).length === 0 && context.selectedCoverageOption.problem === "THEFT"
},
hasArticles: (context, _event) => context.allArticles.length > 0,
// checking if at least manufacturer was provided but in results is no article
noMatchingArticles: (context, _event) => context.filteredArticles.current.length === 0
&& context.filter.manufacturer !== '',
manufacturerIsSet: (context, _event) => context.filter.manufacturer !== '',
categoryIsSet: (context, _event) => context.filter.category !== '',
seriesIsSet: (context, _event) => context.filter.series !== '',
modelIsSet: (context, _event) => context.filter.model !== '',
// fallback article should always use 0 as `id` for article definition
shouldUseFallbackArticle: (_context, event) => event.value === 'OTHER',
shouldHidePrice: (context, event) => {
const selectedOption = context.allCoverages.find(
(coverage) => coverage.id === event.value,
);
if (selectedOption) {
return (
selectedOption.coverage === 'WARRANTY'
|| selectedOption.coverage === 'INSURANCE'
);
}
return (
context.selectedCoverageOption.coverage === 'WARRANTY'
|| context.selectedCoverageOption.coverage === 'INSURANCE'
);
},
shouldSkipDamages: (context, event) => {
const selectedOption = context.allCoverages.find(
(coverage) => coverage.id === event.value,
);
return selectedOption && selectedOption.problem === 'THEFT';
},
// eslint-disable-next-line max-len
shouldSkipCategory: (context, _event) => context.filteredArticles.current.every(
(article) => article.category === context.filteredArticles.current[0].category,
),
shouldOmitSeries: (context, _event) => (context.filteredArticles.category
&& context.filteredArticles.category.every(
(article) => article.series === null,
))
|| Object.keys(context.filteredArticles)[0] === 'current',
hasCoverages: (context, _event) => context.allCoverages.length > 0,
},
actions: {
hidePrice: assign({ isPriceVisible: false }),
showPrice: assign({ isPriceVisible: true }),
// TODO: this should only remove model and not all filters see REP-201
removeModel: assign((_context, _event) => ({
filter: {
manufacturer: '',
category: '',
series: '',
model: '',
damage: '',
},
filteredArticles: {
manufacturer: [],
category: [],
series: [],
model: [],
damage: [],
current: [],
},
selectedCoverageOption: {},
})),
selectCoverageOption: assign((context, event) => ({
// eslint-disable-next-line max-len
selectedCoverageOption: context.allCoverages.find(
(coverage) => coverage.id === event.value,
),
filter: {
...context.filter,
damage: '',
},
})),
autoSelectColor: assign((context, event) => {
let items = [];
if(context.selectedCoverageOption.problem === 'THEFT'){
items = context.filteredArticles.model
} else {
items = context.filteredArticles.damage
}
const colors = items.filter(item => item.color !== null).map(item => item.color)
const uniqueColor = Array.from(new Set(colors))[0]
const autoEvent = {
type: 'SELECTCOLOR',
value: uniqueColor,
};
const filter = setFilter(autoEvent, context.filter);
return {
filter,
filteredArticles: applyFilter(filter, autoEvent, context),
};
}),
autoSetCategory: assign((context, _event) => {
const autoEvent = {
type: 'SELECTCATEGORY',
value: context.filteredArticles.current[0].category,
};
const filter = setFilter(autoEvent, context.filter);
return {
filter,
filteredArticles: applyFilter(filter, autoEvent, context),
};
}),
selectFilter: assign((context, event) => {
const filter = setFilter(event, context.filter);
return {
filter,
filteredArticles: applyFilter(filter, event, context),
};
}),
setAllArticles: assign({
allArticles: (context, event) => {
let dummyArticle = event.data.data.map((item) => ({
id: 0,
damage: 'OTHER',
articlenumber: '000',
manufacturernumber: '000',
articlename: 'OTHER',
color: item.color,
manufacturer: item.manufacturer,
model: item.model,
series: item.series,
category: item.category,
price: 0,
pricevat: 0,
}));
dummyArticle = [
...new Set(dummyArticle.map((item) => JSON.stringify(item))),
].map((item) => JSON.parse(item));
return [...event.data.data, ...dummyArticle];
},
}),
setAllCoverages: assign({
allCoverages: (context, event) => event.data.data,
}),
filterArticles: assign((context, event) => ({
filteredArticles: applyFilter(context.filter, event, context),
})),
},
},
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment