Files
cozsweet-frontend-nextjs/src/stores/payment/payment-context.tsx
T
Codex 7ed97af68e
Docker Image / Quality and Bundle Budgets (push) Successful in 3s
Docker Image / Build and Push Docker Image (push) Has been cancelled
Add chat gifts and relationship diary UI
2026-07-21 18:41:34 +08:00

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",
};
}