refactor(stores): standardize state machine structure

This commit is contained in:
2026-07-15 11:19:08 +08:00
parent ed3e800245
commit 20d07023e4
68 changed files with 3492 additions and 3193 deletions
+212
View File
@@ -0,0 +1,212 @@
import type { DoneActorEvent, ErrorActorEvent } from "xstate";
import type { PaymentPlansResponse } from "@/data/dto/payment";
import {
consumeFirstRechargeState,
hydratePlansResponseState,
refreshPlansResponseState,
selectPlanState,
} from "../helper/catalog";
import {
resetOrderState,
toPaymentErrorMessage,
} from "../helper/order";
import {
basePaymentMachineSetup,
createPaymentActorActionSetup,
} from "./setup";
const initializeCatalogAction = basePaymentMachineSetup.assign(
({ context, event }) => {
if (event.type !== "PaymentInit") return {};
return {
planCatalog: event.catalog ?? context.planCatalog,
...(event.payChannel ? { payChannel: event.payChannel } : {}),
};
},
);
const refreshCatalogAction = basePaymentMachineSetup.assign(
({ context, event }) => {
if (event.type !== "PaymentInit") return {};
const planCatalog = event.catalog ?? context.planCatalog;
const catalogChanged = planCatalog !== context.planCatalog;
return {
planCatalog,
...(event.payChannel ? { payChannel: event.payChannel } : {}),
...(catalogChanged
? {
plans: [],
selectedPlanId: "",
isFirstRecharge: false,
autoRenew: planCatalog !== "tip",
}
: {}),
};
},
);
const selectPlanAction = basePaymentMachineSetup.assign(
({ context, event }) =>
event.type === "PaymentPlanSelected"
? selectPlanState(event.planId, context.plans)
: {},
);
const changePayChannelAction = basePaymentMachineSetup.assign(({ event }) =>
event.type === "PaymentPayChannelChanged"
? {
...resetOrderState(),
payChannel: event.payChannel,
errorMessage: null,
}
: {},
);
const changeAutoRenewAction = basePaymentMachineSetup.assign(({ event }) =>
event.type === "PaymentAutoRenewChanged"
? { autoRenew: event.autoRenew, errorMessage: null }
: {},
);
const changeAgreementAction = basePaymentMachineSetup.assign(({ event }) =>
event.type === "PaymentAgreementChanged"
? { agreed: event.agreed, errorMessage: null }
: {},
);
const showCreateOrderValidationErrorAction = basePaymentMachineSetup.assign({
errorMessage: "Choose a plan and accept the agreement before continuing.",
});
const clearPaymentErrorAction = basePaymentMachineSetup.assign({
errorMessage: null,
});
const resetOrderAction = basePaymentMachineSetup.assign(() =>
resetOrderState(),
);
const consumeFirstRechargeAction = basePaymentMachineSetup.assign(
({ context }) => consumeFirstRechargeState(context),
);
export const catalogMachineSetup = basePaymentMachineSetup.extend({
actions: {
initializeCatalog: initializeCatalogAction,
refreshCatalog: refreshCatalogAction,
selectPlan: selectPlanAction,
changePayChannel: changePayChannelAction,
changeAutoRenew: changeAutoRenewAction,
changeAgreement: changeAgreementAction,
showCreateOrderValidationError: showCreateOrderValidationErrorAction,
clearPaymentError: clearPaymentErrorAction,
resetOrder: resetOrderAction,
consumeFirstRecharge: consumeFirstRechargeAction,
},
});
const cachedPlansDoneSetup =
createPaymentActorActionSetup<
DoneActorEvent<PaymentPlansResponse | null>
>();
const plansDoneSetup =
createPaymentActorActionSetup<DoneActorEvent<PaymentPlansResponse>>();
const plansErrorSetup = createPaymentActorActionSetup<ErrorActorEvent>();
const hydrateCachedPlansAction = cachedPlansDoneSetup.assign(
({ context, event }) =>
event.output
? hydratePlansResponseState(event.output, context.selectedPlanId)
: {},
);
const hydratePlansAction = plansDoneSetup.assign(({ context, event }) =>
hydratePlansResponseState(event.output, context.selectedPlanId),
);
const refreshPlansAction = plansDoneSetup.assign(({ context, event }) =>
refreshPlansResponseState(event.output, context.selectedPlanId),
);
const applyPlansErrorAction = plansErrorSetup.assign(({ event }) => ({
errorMessage: toPaymentErrorMessage(event.error),
}));
export const idleState = catalogMachineSetup.createStateConfig({
on: {
PaymentInit: {
target: "loadingCachedPlans",
actions: "initializeCatalog",
},
},
});
export const loadingCachedPlansState =
catalogMachineSetup.createStateConfig({
invoke: {
src: "loadCachedPlans",
input: ({ context }) => ({ catalog: context.planCatalog }),
onDone: [
{
guard: ({ event }) => (event.output?.plans.length ?? 0) > 0,
target: "refreshingPlans",
actions: hydrateCachedPlansAction,
},
{ target: "loadingPlans" },
],
onError: { target: "loadingPlans" },
},
});
export const loadingPlansState = catalogMachineSetup.createStateConfig({
invoke: {
src: "refreshPlans",
input: ({ context }) => ({ catalog: context.planCatalog }),
onDone: {
target: "ready",
actions: hydratePlansAction,
},
onError: {
target: "ready",
actions: applyPlansErrorAction,
},
},
});
export const refreshingPlansState = catalogMachineSetup.createStateConfig({
invoke: {
src: "refreshPlans",
input: ({ context }) => ({ catalog: context.planCatalog }),
onDone: {
target: "ready",
actions: refreshPlansAction,
},
onError: {
target: "ready",
actions: applyPlansErrorAction,
},
},
});
export const readyState = catalogMachineSetup.createStateConfig({
on: {
PaymentInit: {
target: "refreshingPlans",
actions: "refreshCatalog",
},
PaymentPlanSelected: { actions: "selectPlan" },
PaymentPayChannelChanged: { actions: "changePayChannel" },
PaymentAutoRenewChanged: { actions: "changeAutoRenew" },
PaymentAgreementChanged: { actions: "changeAgreement" },
PaymentCreateOrderSubmitted: [
{
guard: "canCreateOrder",
target: "creatingOrder",
},
{ actions: "showCreateOrderValidationError" },
],
PaymentErrorCleared: { actions: "clearPaymentError" },
},
});