import type { DoneActorEvent, ErrorActorEvent } from "xstate"; import type { PrivateAlbumUnlockResponse } from "@/data/schemas/private-zone"; import { getPendingAlbum, patchAlbumInList, removeAlbum, } from "../helper/albums"; import { toPaywallRequest, toPrivateZoneErrorMessage, toUnlockErrorMessage, } from "../helper/unlock"; import { albumMachineSetup, failedState, idleState, loadingState, } from "./album-flow"; import { createPrivateZoneActorActionSetup } from "./setup"; const requestUnlockAction = albumMachineSetup.assign(({ event }) => event.type === "PrivateZoneUnlockRequested" ? { pendingConfirmAlbumId: event.albumId, unlockErrorMessage: null, unlockPaywallRequest: null, } : {}, ); const cancelUnlockAction = albumMachineSetup.assign({ pendingConfirmAlbumId: null, }); const clearUnlockPaywallAction = albumMachineSetup.assign({ unlockPaywallRequest: null, }); const beginUnlockAction = albumMachineSetup.assign(({ context }) => ({ unlockingAlbumId: context.pendingConfirmAlbumId, unlockErrorMessage: null, unlockPaywallRequest: null, })); export const privateZoneMachineSetup = albumMachineSetup.extend({ actions: { requestUnlock: requestUnlockAction, cancelUnlock: cancelUnlockAction, clearUnlockPaywall: clearUnlockPaywallAction, beginUnlock: beginUnlockAction, }, }); const unlockDoneSetup = createPrivateZoneActorActionSetup< DoneActorEvent >(); const unlockErrorSetup = createPrivateZoneActorActionSetup(); const applyUnlockSuccessAction = unlockDoneSetup.assign( ({ context, event }) => ({ items: patchAlbumInList(context.items, event.output), creditBalance: event.output.creditBalance, pendingConfirmAlbumId: null, unlockingAlbumId: null, unlockErrorMessage: null, unlockSuccessNonce: context.unlockSuccessNonce + 1, }), ); const applyUnlockPaywallAction = unlockDoneSetup.assign( ({ context, event }) => ({ items: patchAlbumInList(context.items, event.output), creditBalance: event.output.creditBalance, pendingConfirmAlbumId: null, unlockingAlbumId: null, unlockErrorMessage: null, unlockPaywallRequest: toPaywallRequest(event.output), }), ); const applyCostChangedAction = unlockDoneSetup.assign(({ event }) => ({ pendingConfirmAlbumId: null, unlockingAlbumId: null, unlockErrorMessage: toUnlockErrorMessage(event.output), })); const applyAlbumNotFoundAction = unlockDoneSetup.assign( ({ context, event }) => ({ items: removeAlbum(context.items, event.output.albumId), creditBalance: event.output.creditBalance, pendingConfirmAlbumId: null, unlockingAlbumId: null, unlockErrorMessage: toUnlockErrorMessage(event.output), }), ); const applyUnlockFailureAction = unlockDoneSetup.assign( ({ context, event }) => ({ items: patchAlbumInList(context.items, event.output), creditBalance: event.output.creditBalance, pendingConfirmAlbumId: null, unlockingAlbumId: null, unlockErrorMessage: toUnlockErrorMessage(event.output), }), ); const applyUnlockActorErrorAction = unlockErrorSetup.assign(({ event }) => ({ pendingConfirmAlbumId: null, unlockingAlbumId: null, unlockErrorMessage: toPrivateZoneErrorMessage(event.error), })); const readyState = privateZoneMachineSetup.createStateConfig({ on: { PrivateZoneInit: "loading", PrivateZoneRefresh: "loading", PrivateZoneUnlockRequested: { actions: "requestUnlock" }, PrivateZoneUnlockCancelled: { actions: "cancelUnlock" }, PrivateZoneUnlockConfirmed: [ { guard: "hasPendingAlbum", target: "unlocking", }, ], PrivateZoneUnlockPaywallConsumed: { actions: "clearUnlockPaywall" }, }, }); const unlockingState = privateZoneMachineSetup.createStateConfig({ entry: "beginUnlock", invoke: { src: "unlockAlbum", input: ({ context }) => { const pendingAlbum = getPendingAlbum(context); return { albumId: pendingAlbum?.albumId ?? "", expectedCost: pendingAlbum?.unlockCost ?? 0, }; }, onDone: [ { guard: ({ event }) => event.output.unlocked && !event.output.locked, target: "ready", actions: applyUnlockSuccessAction, }, { guard: ({ event }) => event.output.reason === "insufficient_credits", target: "ready", actions: applyUnlockPaywallAction, }, { guard: ({ event }) => event.output.reason === "cost_changed", target: "loading", actions: applyCostChangedAction, }, { guard: ({ event }) => event.output.reason === "not_found", target: "ready", actions: applyAlbumNotFoundAction, }, { target: "ready", actions: applyUnlockFailureAction, }, ], onError: { target: "ready", actions: applyUnlockActorErrorAction, }, }, }); export const privateZoneRootStateConfig = privateZoneMachineSetup.createStateConfig({ initial: "idle", states: { idle: idleState, loading: loadingState, failed: failedState, ready: readyState, unlocking: unlockingState, }, });