Files
cozsweet-frontend-nextjs/src/stores/private-room/private-room-context.tsx
T

83 lines
2.5 KiB
TypeScript

"use client";
import type { Dispatch, ReactNode } from "react";
import { createActorContext, shallowEqual } from "@xstate/react";
import type { SnapshotFrom } from "xstate";
import { privateRoomMachine } from "./private-room-machine";
import type {
PrivateRoomEvent,
PrivateRoomState as MachineContext,
} from "./private-room-machine";
/** Route UI reads this projection instead of the Private Room snapshot. */
export interface PrivateRoomContextState {
characterId: string;
status: string;
items: MachineContext["items"];
creditBalance: number;
errorMessage: string | null;
unlockingAlbumId: string | null;
unlockErrorMessage: string | null;
pendingConfirmAlbumId: string | null;
unlockPaywallRequest: MachineContext["unlockPaywallRequest"];
unlockSuccessNonce: number;
isLoading: boolean;
isUnlocking: boolean;
}
type PrivateRoomSnapshot = SnapshotFrom<typeof privateRoomMachine>;
type PrivateRoomSelector<T> = (snapshot: PrivateRoomSnapshot) => T;
const PrivateRoomActorContext = createActorContext(privateRoomMachine);
export interface PrivateRoomProviderProps {
children: ReactNode;
characterId: string;
}
export function PrivateRoomProvider({
children,
characterId,
}: PrivateRoomProviderProps) {
return (
<PrivateRoomActorContext.Provider options={{ input: { characterId } }}>
{children}
</PrivateRoomActorContext.Provider>
);
}
export function usePrivateRoomState(): PrivateRoomContextState {
return usePrivateRoomSelector(selectPrivateRoomState, shallowEqual);
}
export function usePrivateRoomDispatch(): Dispatch<PrivateRoomEvent> {
return PrivateRoomActorContext.useActorRef().send;
}
export function usePrivateRoomSelector<T>(
selector: PrivateRoomSelector<T>,
compare?: (previous: T, next: T) => boolean,
): T {
return PrivateRoomActorContext.useSelector(selector, compare);
}
function selectPrivateRoomState(
state: PrivateRoomSnapshot,
): PrivateRoomContextState {
return {
characterId: state.context.characterId,
status: String(state.value),
items: state.context.items,
creditBalance: state.context.creditBalance,
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"),
};
}