Files
cozsweet-frontend-nextjs/src/stores/private-zoom/private-zoom-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 { privateZoomMachine } from "./private-zoom-machine";
import type {
PrivateZoomEvent,
PrivateZoomState as MachineContext,
} from "./private-zoom-machine";
/** Route UI reads this projection instead of the Private Zoom snapshot. */
export interface PrivateZoomContextState {
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 PrivateZoomSnapshot = SnapshotFrom<typeof privateZoomMachine>;
type PrivateZoomSelector<T> = (snapshot: PrivateZoomSnapshot) => T;
const PrivateZoomActorContext = createActorContext(privateZoomMachine);
export interface PrivateZoomProviderProps {
children: ReactNode;
characterId: string;
}
export function PrivateZoomProvider({
children,
characterId,
}: PrivateZoomProviderProps) {
return (
<PrivateZoomActorContext.Provider options={{ input: { characterId } }}>
{children}
</PrivateZoomActorContext.Provider>
);
}
export function usePrivateZoomState(): PrivateZoomContextState {
return usePrivateZoomSelector(selectPrivateZoomState, shallowEqual);
}
export function usePrivateZoomDispatch(): Dispatch<PrivateZoomEvent> {
return PrivateZoomActorContext.useActorRef().send;
}
export function usePrivateZoomSelector<T>(
selector: PrivateZoomSelector<T>,
compare?: (previous: T, next: T) => boolean,
): T {
return PrivateZoomActorContext.useSelector(selector, compare);
}
function selectPrivateZoomState(
state: PrivateZoomSnapshot,
): PrivateZoomContextState {
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"),
};
}