Last active
October 2, 2019 10:37
-
-
Save mikaelkaron/adf20c79b13905d83a994668bcf29dca to your computer and use it in GitHub Desktop.
Generated by XState Viz: https://xstate.js.org/viz
This file contains hidden or 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
| // Available variables: | |
| // - Machine | |
| // - interpret | |
| // - assign | |
| // - send | |
| // - sendParent | |
| // - spawn | |
| // - raise | |
| // - actions | |
| // - XState (all XState exports) | |
| const boot = { | |
| id: "boot", | |
| initial: "pending", | |
| states: { | |
| pending: { | |
| invoke: { | |
| src: "boot", | |
| onDone: { target: "success", actions: "login" }, | |
| onError: "#error" | |
| }, | |
| after: { TIMEOUT: "#error" } | |
| }, | |
| success: { type: "final" }, | |
| }, | |
| onDone: [{ target: "auth", cond: "isAuth" }, { target: "anon" }], | |
| meta: { component: "page-boot" } | |
| }; | |
| const signup = { | |
| invoke: { | |
| src: "signup", | |
| autoForward: true, | |
| onDone: { | |
| target: "done", | |
| actions: "login" | |
| } | |
| }, | |
| on: { | |
| SIGNIN: "signin" | |
| }, | |
| meta: { component: "page-signup" } | |
| }; | |
| const signin = { | |
| invoke: { | |
| src: "signin", | |
| autoForward: true, | |
| onDone: { | |
| target: "done", | |
| actions: "login" | |
| } | |
| }, | |
| on: { | |
| SIGNUP: "signup" | |
| }, | |
| meta: { component: "page-signin" } | |
| }; | |
| const anon = { | |
| id: "anon", | |
| initial: "go", | |
| states: { | |
| go: { | |
| on: { | |
| "": [ | |
| { target: "signup", cond: "goSignup" }, | |
| { target: "signin" } | |
| ] | |
| } | |
| }, | |
| signup, | |
| signin, | |
| done: { type: "final" } | |
| }, | |
| onDone: "#auth", | |
| on: { "": { target: "boot", cond: "isAuth" } } | |
| }; | |
| const AccountPage = ({ id, component = `page-${id}`, incomplete, complete, target }, ...actions) => ({ | |
| id, | |
| initial: "incomplete", | |
| states: { | |
| incomplete: { | |
| invoke: { | |
| src: "account", | |
| autoForward: true, | |
| data: ({ account }) => account, | |
| onDone: { | |
| target: "complete", | |
| actions: [ "accountUpdate", ...actions ] | |
| } | |
| }, | |
| on: { | |
| "": { | |
| target: "complete", | |
| cond: complete | |
| } | |
| } | |
| }, | |
| complete: { type: "final" } | |
| }, | |
| onDone: [ | |
| { target: ".incomplete", cond: incomplete }, | |
| { target } | |
| ], | |
| meta: { component } | |
| }); | |
| const welcome = AccountPage({ | |
| id: "welcome", | |
| incomplete: "welcomePending", | |
| complete: "welcomeCompleted", | |
| target: "enrolled" | |
| }); | |
| const onboarding = AccountPage({ | |
| id: "onboarding", | |
| incomplete: "onboardingPending", | |
| complete: "onboardingCompleted", | |
| target: "apply" | |
| }); | |
| const application = { | |
| initial: "local", | |
| states: { | |
| local: { | |
| on: { | |
| "": [ | |
| { target: "success", cond: "hasApplication" }, | |
| { target: "remote" } | |
| ] | |
| } | |
| }, | |
| remote: { | |
| invoke: { | |
| src: "applicationRead", | |
| onDone: [ | |
| { target: "create", cond: "noData" }, | |
| { target: "success", actions: "applicationUpdate" } | |
| ], | |
| onError: "#error" | |
| }, | |
| after: { TIMEOUT: "#error" } | |
| }, | |
| create: { | |
| invoke: { | |
| src: "applicationCreate", | |
| onDone: { target: "success", actions: "applicationUpdate" }, | |
| onError: "#error" | |
| }, | |
| after: { TIMEOUT: "#error" } | |
| }, | |
| success: { type: "final" } | |
| }, | |
| onDone: [ | |
| { target: "evaluation", cond: "evaluationPending" }, | |
| { target: "references", cond: "referencesPending" }, | |
| { target: "profile" } | |
| ] | |
| }; | |
| const ApplicationPage = ({ id, component = `page-${id}`, incomplete, complete }, ...actions) => ({ | |
| id, | |
| initial: "edit", | |
| states: { | |
| edit: { | |
| invoke: { | |
| src: "application", | |
| autoForward: true, | |
| data: ({ application }) => application, | |
| onDone: { | |
| target: "done", | |
| actions: [ "applicationUpdate", ...actions ] | |
| } | |
| }, | |
| initial: "incomplete", | |
| states: { | |
| incomplete: { | |
| on: { | |
| "": { | |
| target: "complete", | |
| cond: complete | |
| } | |
| } | |
| }, | |
| complete: { | |
| on: { | |
| NEXT: "done" | |
| } | |
| }, | |
| done: { type: "final" } | |
| }, | |
| onDone: "done" | |
| }, | |
| done: { type: "final" } | |
| }, | |
| onDone: [ | |
| { target: ".edit", cond: incomplete }, | |
| { target: "application" } | |
| ], | |
| meta: { component } | |
| }); | |
| const evaluation = ApplicationPage({ | |
| id: "evaluation", | |
| incomplete: "evaluationPending", | |
| complete: "evaluationCompleted" | |
| }, "completeEvaluation"); | |
| const references = ApplicationPage({ | |
| id: "references", | |
| incomplete: "referencesPending", | |
| complete: "referencesCompleted" | |
| }, "completeReferences"); | |
| const profile = { | |
| meta: { component: "page-profile" } | |
| }; | |
| const apply = { | |
| initial: "application", | |
| states: { | |
| application, | |
| evaluation, | |
| references, | |
| profile | |
| }, | |
| on: { | |
| EVALUATION: ".evaluation", | |
| REFERENCES: { | |
| target: ".references", | |
| cond: "evaluationCompleted" | |
| }, | |
| PROFILE: { | |
| target: ".profile", | |
| cond: "referencesCompleted" | |
| } | |
| } | |
| }; | |
| const enrolled = { | |
| initial: "onboarding", | |
| states: { | |
| onboarding, | |
| apply | |
| }, | |
| on: { | |
| ACCOUNT: "#account" | |
| } | |
| }; | |
| const active = { | |
| initial: "welcome", | |
| states: { | |
| welcome, | |
| enrolled, | |
| hist: { | |
| id: "hist", | |
| type: "history", | |
| history: "deep" | |
| } | |
| } | |
| }; | |
| const account = { | |
| id: "account", | |
| initial: "edit", | |
| states: { | |
| edit: { | |
| invoke: { | |
| src: "account", | |
| autoForward: true, | |
| data: ({ account }) => account, | |
| onDone: { | |
| target: "done", | |
| actions: "accountUpdate" | |
| } | |
| }, | |
| on: { | |
| CANCEL: "done" | |
| } | |
| }, | |
| done: { type: "final" } | |
| }, | |
| onDone: "#hist", | |
| meta: { component: "page-account" } | |
| }; | |
| const signout = { | |
| initial: "pending", | |
| states: { | |
| pending: { | |
| invoke: { | |
| src: "signout", | |
| onDone: "success", | |
| onError: "failure" | |
| }, | |
| after: { TIMEOUT: "failure" } | |
| }, | |
| success: { type: "final" }, | |
| failure: { | |
| on: { | |
| "": "#hist" | |
| } | |
| } | |
| }, | |
| onDone: { target: "done", actions: "logout" }, | |
| meta: { component: "page-signout" } | |
| }; | |
| const auth = { | |
| id: "auth", | |
| initial: "active", | |
| states: { | |
| active, | |
| account, | |
| signout, | |
| done: { type: "final" } | |
| }, | |
| onDone: "#anon", | |
| on: { | |
| "": { | |
| target: "#boot", | |
| cond: "isAnon" | |
| }, | |
| SIGNOUT: ".signout" | |
| } | |
| }; | |
| const error = { | |
| id: "error", | |
| type: "final", | |
| meta: { component: "page-error" } | |
| }; | |
| const config = { | |
| id: "app", | |
| initial: "boot", | |
| entry: "params", | |
| context: {}, | |
| states: { | |
| boot, | |
| anon, | |
| auth, | |
| error | |
| }, | |
| on: { | |
| BOOT: { | |
| target: '.boot', | |
| actions: 'logout' | |
| } | |
| } | |
| }; | |
| const Step = { | |
| none: 0, | |
| evaluation: 1, | |
| references: 2 | |
| }; | |
| const Flag = { | |
| none: 0, | |
| tc: 1, | |
| onboarding: 2 | |
| }; | |
| const set = flag => assign({ | |
| account: ({ account }) => ({ | |
| ...account, | |
| flags: account.flags | flag | |
| }) | |
| }); | |
| const has = flag => ({ account: { flags } = {} }) => !!(flags & flag); | |
| const not = flag => ({ account: { flags } = {} }) => !(flags & flag); | |
| const complete = step => assign({ | |
| application: ({ application = {} }) => ({ | |
| ...application, | |
| completed: application.completed | step | |
| }) | |
| }); | |
| const completed = step => ({ application: { completed } = {} }) => | |
| !!(completed & step); | |
| const pending = step => ({ application: { completed } = {} }) => | |
| !(completed & step); | |
| const InputMachine = (submit, TIMEOUT = 2000) => Machine( | |
| { | |
| initial: "ready", | |
| states: { | |
| ready: { | |
| entry: sendParent("READY"), | |
| on: { | |
| SUBMIT: "pending" | |
| } | |
| }, | |
| pending: { | |
| entry: [sendParent("PENDING"), "pending"], | |
| invoke: { | |
| src: "submit", | |
| onDone: "success", | |
| onError: { | |
| target: "failure", | |
| actions: "error" | |
| }, | |
| data: ctx => ctx | |
| }, | |
| after: { | |
| TIMEOUT: { | |
| target: "failure", | |
| actions: "timeout" | |
| } | |
| } | |
| }, | |
| success: { | |
| type: "final", | |
| data: (ctx, event) => event.data | |
| }, | |
| failure: { | |
| entry: sendParent(({ error }) => ({ type: "ERROR", error })), | |
| on: { | |
| "": "ready" | |
| } | |
| } | |
| } | |
| }, | |
| { | |
| actions: { | |
| pending: assign({ error: undefined }), | |
| error: assign({ | |
| error: (_context, event) => event.data.message | |
| }), | |
| timeout: assign({ error: "Timeout: No response from backend" }) | |
| }, | |
| delays: { | |
| TIMEOUT | |
| }, | |
| services: { | |
| submit | |
| } | |
| } | |
| ); | |
| const services = { | |
| boot: Promise.resolve({}), | |
| signin: InputMachine((ctx, { email }) => Promise.resolve({ account: { email, flags: Flag.tc }, application: { completed: Step.evaluation }})), | |
| signup: InputMachine((ctx, { email }) => Promise.resolve({ account: { email, flags: Flag.none }})), | |
| signout: Promise.resolve('bye'), | |
| account: InputMachine((account, { type, ...event }) => Promise.resolve({ ...account, ...event }) | |
| ), | |
| application: InputMachine((application, { type, ...event }) => Promise.resolve({ ...application, ...event }) | |
| ), | |
| applicationRead: ({ application }) => Promise.resolve(application), | |
| applicationCreate: Promise.resolve({ completed: Step.none }) | |
| }; | |
| const delays = { | |
| TIMEOUT: 2000 | |
| }; | |
| const options = { | |
| actions: { | |
| login: assign((ctx, { data }) => ({ ...ctx, ...data })), | |
| logout: assign({ | |
| account: undefined, | |
| profile: undefined, | |
| application: undefined | |
| }), | |
| accountUpdate: assign({ account: (ctx, { data }) => ({ ...data }) }), | |
| applicationUpdate: assign({ application: (ctx, { data }) => ({ ...data }) }), | |
| welcomeComplete: set(Flag.tc), | |
| onboardingComplete: set(Flag.onboarding), | |
| evaluationComplete: complete(Step.evaluation), | |
| referencesComplete: complete(Step.references) | |
| }, | |
| guards: { | |
| isAnon: ctx => !ctx.account, | |
| isAuth: ctx => !!ctx.account, | |
| noData: (_ctx, event) => !event.data, | |
| goSignup: () => false, | |
| hasApplication: ctx => !!ctx.application, | |
| welcomeCompleted: has(Flag.tc), | |
| welcomePending: not(Flag.tc), | |
| onboardingCompleted: has(Flag.onboarding), | |
| onboardingPending: not(Flag.onboarding), | |
| evaluationPending: pending(Step.evaluation), | |
| evaluationCompleted: completed(Step.evaluation), | |
| referencesPending: pending(Step.references), | |
| referencesCompleted: completed(Step.references) | |
| }, | |
| services, | |
| delays | |
| }; | |
| // debug | |
| account.states.edit.on.UPDATE = { | |
| actions: send({ type: "SUBMIT", "email": "rose@karon.se" }) | |
| }; | |
| signin.on.SIGNIN = { | |
| actions: send({ type: "SUBMIT", "email": "mikael@karon.se" }) | |
| }; | |
| signup.on.SIGNUP = { | |
| actions: send({ type: "SUBMIT", "email": "e@mail.com" }) | |
| }; | |
| welcome.states.incomplete.on.UPDATE = { | |
| actions: send({ | |
| type: "SUBMIT", | |
| mobile: 123456, | |
| flags: Flag.tc | |
| }) | |
| }; | |
| onboarding.states.incomplete.on.NEXT = { | |
| actions: send({ | |
| type: "SUBMIT", | |
| flags: Flag.onboarding | |
| }) | |
| }; | |
| evaluation.states.edit.on = { SAVE: { | |
| actions: send({ | |
| type: "SUBMIT", | |
| completed: Step.evaluation, | |
| evaluation: { | |
| "some": "evaluation" | |
| } | |
| }) | |
| } }; | |
| references.states.edit.on = { SAVE: { | |
| actions: send({ | |
| type: "SUBMIT", | |
| completed: Step.evaluation | Step.references, | |
| references: { | |
| "moar": "references" | |
| } | |
| }) | |
| } }; | |
| const app = Machine(config, options); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment