Skip to content

Instantly share code, notes, and snippets.

@mikaelkaron
Last active October 2, 2019 10:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mikaelkaron/adf20c79b13905d83a994668bcf29dca to your computer and use it in GitHub Desktop.
Save mikaelkaron/adf20c79b13905d83a994668bcf29dca to your computer and use it in GitHub Desktop.
Generated by XState Viz: https://xstate.js.org/viz
// 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