"use client"; import type { Dispatch, ReactNode } from "react"; import { createActorContext, shallowEqual } from "@xstate/react"; import type { SnapshotFrom } from "xstate"; import { privateZoneMachine } from "./private-zone-machine"; import type { PrivateZoneEvent, PrivateZoneState as MachineContext, } from "./private-zone-machine"; /** Route UI reads this projection instead of the Private Zone snapshot. */ export interface PrivateZoneContextState { characterId: string; status: string; items: MachineContext["items"]; creditBalance: number; pendingImageCount: number; hasIncompleteContent: boolean; errorMessage: string | null; unlockingAlbumId: string | null; unlockErrorMessage: string | null; pendingConfirmAlbumId: string | null; unlockPaywallRequest: MachineContext["unlockPaywallRequest"]; unlockSuccessNonce: number; isLoading: boolean; isUnlocking: boolean; } type PrivateZoneSnapshot = SnapshotFrom; type PrivateZoneSelector = (snapshot: PrivateZoneSnapshot) => T; const PrivateZoneActorContext = createActorContext(privateZoneMachine); export interface PrivateZoneProviderProps { children: ReactNode; characterId: string; } export function PrivateZoneProvider({ children, characterId, }: PrivateZoneProviderProps) { return ( {children} ); } export function usePrivateZoneState(): PrivateZoneContextState { return usePrivateZoneSelector(selectPrivateZoneState, shallowEqual); } export function usePrivateZoneDispatch(): Dispatch { return PrivateZoneActorContext.useActorRef().send; } export function usePrivateZoneSelector( selector: PrivateZoneSelector, compare?: (previous: T, next: T) => boolean, ): T { return PrivateZoneActorContext.useSelector(selector, compare); } function selectPrivateZoneState( state: PrivateZoneSnapshot, ): PrivateZoneContextState { return { characterId: state.context.characterId, status: String(state.value), items: state.context.items, creditBalance: state.context.creditBalance, pendingImageCount: state.context.pendingImageCount, hasIncompleteContent: state.context.hasIncompleteContent, errorMessage: state.context.errorMessage, unlockingAlbumId: state.context.unlockingAlbumId, unlockErrorMessage: state.context.unlockErrorMessage, pendingConfirmAlbumId: state.context.pendingConfirmAlbumId, unlockPaywallRequest: state.context.unlockPaywallRequest, unlockSuccessNonce: state.context.unlockSuccessNonce, isLoading: state.matches("loading"), isUnlocking: state.matches("unlocking"), }; }