102 lines
3.4 KiB
TypeScript
102 lines
3.4 KiB
TypeScript
"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<string, unknown> | 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<typeof paymentMachine>;
|
|
type PaymentSelector<T> = (snapshot: PaymentSnapshot) => T;
|
|
|
|
const PaymentActorContext = createActorContext(paymentMachine);
|
|
|
|
export interface PaymentProviderProps {
|
|
children: ReactNode;
|
|
}
|
|
|
|
export function PaymentProvider({ children }: PaymentProviderProps) {
|
|
return <PaymentActorContext.Provider>{children}</PaymentActorContext.Provider>;
|
|
}
|
|
|
|
export function usePaymentState(): PaymentContextState {
|
|
return usePaymentSelector(selectPaymentState, shallowEqual);
|
|
}
|
|
|
|
export function usePaymentDispatch(): Dispatch<PaymentEvent> {
|
|
return PaymentActorContext.useActorRef().send;
|
|
}
|
|
|
|
export function usePaymentSelector<T>(
|
|
selector: PaymentSelector<T>,
|
|
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",
|
|
};
|
|
}
|