Files
cozsweet-frontend-nextjs/src/stores/private-zone/private-zone-context.tsx
T
Codex 770ce6b8fd
Docker Image / Build and Push Docker Image (push) Successful in 1m59s
fix(private-zone): hide incomplete albums and refresh Maya cover
2026-07-23 11:51:14 +08:00

87 lines
2.7 KiB
TypeScript

"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<typeof privateZoneMachine>;
type PrivateZoneSelector<T> = (snapshot: PrivateZoneSnapshot) => T;
const PrivateZoneActorContext = createActorContext(privateZoneMachine);
export interface PrivateZoneProviderProps {
children: ReactNode;
characterId: string;
}
export function PrivateZoneProvider({
children,
characterId,
}: PrivateZoneProviderProps) {
return (
<PrivateZoneActorContext.Provider options={{ input: { characterId } }}>
{children}
</PrivateZoneActorContext.Provider>
);
}
export function usePrivateZoneState(): PrivateZoneContextState {
return usePrivateZoneSelector(selectPrivateZoneState, shallowEqual);
}
export function usePrivateZoneDispatch(): Dispatch<PrivateZoneEvent> {
return PrivateZoneActorContext.useActorRef().send;
}
export function usePrivateZoneSelector<T>(
selector: PrivateZoneSelector<T>,
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"),
};
}