Last active
May 1, 2020 15:57
-
-
Save kevinfoerster/fcbca3fd088453ec7dfb522075491ed0 to your computer and use it in GitHub Desktop.
Generated by XState Viz: https://xstate.js.org/viz
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
// 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