"use client"; /** Route UI reads this projection instead of the Payment machine snapshot. */ import type { Dispatch, ReactNode } from "react"; import { createActorContext, shallowEqual } from "@xstate/react"; import type { SnapshotFrom } from "xstate"; import { paymentMachine } from "./payment-machine"; import type { PaymentEvent, PaymentState as MachineContext, } from "./payment-machine"; export interface PaymentContextState { status: string; planCatalog: MachineContext["planCatalog"]; plans: MachineContext["plans"]; giftCategories: MachineContext["giftCategories"]; giftOffer: MachineContext["giftOffer"]; giftProducts: MachineContext["giftProducts"]; selectedGiftCategory: string | null; isFirstRecharge: MachineContext["isFirstRecharge"]; selectedPlanId: string; payChannel: MachineContext["payChannel"]; autoRenew: boolean; agreed: boolean; currentOrderId: string | null; payParams: Record | null; orderStatus: MachineContext["orderStatus"]; tipMessage: MachineContext["tipMessage"]; tipMessageError: string | null; errorMessage: string | null; launchNonce: number; isLoadingPlans: boolean; isLoadingTipMessage: boolean; isCreatingOrder: boolean; isPollingOrder: boolean; isPaid: boolean; } type PaymentSnapshot = SnapshotFrom; type PaymentSelector = (snapshot: PaymentSnapshot) => T; const PaymentActorContext = createActorContext(paymentMachine); export interface PaymentProviderProps { children: ReactNode; } export function PaymentProvider({ children }: PaymentProviderProps) { return {children}; } export function usePaymentState(): PaymentContextState { return usePaymentSelector(selectPaymentState, shallowEqual); } export function usePaymentDispatch(): Dispatch { return PaymentActorContext.useActorRef().send; } export function usePaymentSelector( selector: PaymentSelector, compare?: (previous: T, next: T) => boolean, ): T { return PaymentActorContext.useSelector(selector, compare); } function selectPaymentState(state: PaymentSnapshot): PaymentContextState { return { status: String(state.value), planCatalog: state.context.planCatalog, plans: state.context.plans, giftCategories: state.context.giftCategories, giftOffer: state.context.giftOffer, giftProducts: state.context.giftProducts, selectedGiftCategory: state.context.selectedGiftCategory, isFirstRecharge: state.context.isFirstRecharge, selectedPlanId: state.context.selectedPlanId, payChannel: state.context.payChannel, autoRenew: state.context.autoRenew, agreed: state.context.agreed, currentOrderId: state.context.currentOrderId, payParams: state.context.payParams, orderStatus: state.context.orderStatus, tipMessage: state.context.tipMessage, tipMessageError: state.context.tipMessageError, errorMessage: state.context.errorMessage, launchNonce: state.context.launchNonce, isLoadingPlans: state.matches("loadingCachedPlans") || state.matches("loadingPlans") || state.matches("loadingGiftProducts") || state.matches("refreshingPlans"), isLoadingTipMessage: state.matches("loadingTipMessage"), isCreatingOrder: state.matches("creatingOrder"), isPollingOrder: state.matches("pollingOrder") || state.matches("waitingForPayment"), isPaid: state.context.orderStatus === "paid", }; }