refactor(private-zoom): rename full feature surface
This commit is contained in:
@@ -1,7 +0,0 @@
|
||||
/** @file Private Room store barrel. */
|
||||
|
||||
export * from "./private-room-context";
|
||||
export * from "./private-room-events";
|
||||
export * from "./helper";
|
||||
export * from "./private-room-machine";
|
||||
export * from "./private-room-state";
|
||||
@@ -1,8 +0,0 @@
|
||||
/** Public events for one character-scoped Private Room actor. */
|
||||
export type PrivateRoomEvent =
|
||||
| { type: "PrivateRoomInit" }
|
||||
| { type: "PrivateRoomRefresh" }
|
||||
| { type: "PrivateRoomUnlockRequested"; albumId: string }
|
||||
| { type: "PrivateRoomUnlockConfirmed" }
|
||||
| { type: "PrivateRoomUnlockCancelled" }
|
||||
| { type: "PrivateRoomUnlockPaywallConsumed" };
|
||||
@@ -1,18 +0,0 @@
|
||||
import {
|
||||
privateRoomMachineSetup,
|
||||
privateRoomRootStateConfig,
|
||||
} from "./machine/unlock-flow";
|
||||
import { createInitialPrivateRoomState } from "./private-room-state";
|
||||
|
||||
export type { PrivateRoomEvent } from "./private-room-events";
|
||||
export type { PrivateRoomState } from "./private-room-state";
|
||||
export { initialState } from "./private-room-state";
|
||||
|
||||
export const privateRoomMachine = privateRoomMachineSetup.createMachine({
|
||||
id: "privateRoom",
|
||||
context: ({ input }) =>
|
||||
createInitialPrivateRoomState(input.characterId),
|
||||
...privateRoomRootStateConfig,
|
||||
});
|
||||
|
||||
export type PrivateRoomMachine = typeof privateRoomMachine;
|
||||
+11
-11
@@ -2,18 +2,18 @@ import { describe, expect, it } from "vitest";
|
||||
import { createActor, waitFor } from "xstate";
|
||||
|
||||
import {
|
||||
createTestPrivateRoomMachine,
|
||||
loadPrivateRoom,
|
||||
createTestPrivateZoomMachine,
|
||||
loadPrivateZoom,
|
||||
makeAlbum,
|
||||
makeAlbumsResponse,
|
||||
TEST_PRIVATE_ROOM_INPUT,
|
||||
} from "./private-room-machine.test-utils";
|
||||
TEST_PRIVATE_ZOOM_INPUT,
|
||||
} from "./private-zoom-machine.test-utils";
|
||||
|
||||
describe("private room album flow", () => {
|
||||
describe("private zoom album flow", () => {
|
||||
it("loads albums for the supplied character", async () => {
|
||||
let loadedCharacterId: string | null = null;
|
||||
const actor = createActor(
|
||||
createTestPrivateRoomMachine({
|
||||
createTestPrivateZoomMachine({
|
||||
onLoad: ({ characterId }) => {
|
||||
loadedCharacterId = characterId;
|
||||
},
|
||||
@@ -21,7 +21,7 @@ describe("private room album flow", () => {
|
||||
{ input: { characterId: "character_aria" } },
|
||||
).start();
|
||||
|
||||
actor.send({ type: "PrivateRoomInit" });
|
||||
actor.send({ type: "PrivateZoomInit" });
|
||||
await waitFor(actor, (snapshot) => snapshot.matches("ready"));
|
||||
|
||||
expect(actor.getSnapshot().context.characterId).toBe("character_aria");
|
||||
@@ -30,7 +30,7 @@ describe("private room album flow", () => {
|
||||
});
|
||||
|
||||
it("loads only the first non-paginated album page", async () => {
|
||||
const actor = await loadPrivateRoom({
|
||||
const actor = await loadPrivateZoom({
|
||||
loadResponse: makeAlbumsResponse({
|
||||
items: Array.from({ length: 20 }, (_, index) => makeAlbum(index)),
|
||||
}),
|
||||
@@ -43,12 +43,12 @@ describe("private room album flow", () => {
|
||||
|
||||
it("moves to failed when the album actor rejects", async () => {
|
||||
const actor = createActor(
|
||||
createTestPrivateRoomMachine({
|
||||
createTestPrivateZoomMachine({
|
||||
loadError: new Error("albums unavailable"),
|
||||
}),
|
||||
{ input: TEST_PRIVATE_ROOM_INPUT },
|
||||
{ input: TEST_PRIVATE_ZOOM_INPUT },
|
||||
).start();
|
||||
actor.send({ type: "PrivateRoomInit" });
|
||||
actor.send({ type: "PrivateZoomInit" });
|
||||
await waitFor(actor, (snapshot) => snapshot.matches("failed"));
|
||||
expect(actor.getSnapshot().context.errorMessage).toBe(
|
||||
"albums unavailable",
|
||||
+13
-13
@@ -8,13 +8,13 @@ import {
|
||||
type PrivateAlbumUnlockResponseInput,
|
||||
type PrivateAlbumsResponse,
|
||||
type PrivateAlbumsResponseInput,
|
||||
} from "@/data/schemas/private-room";
|
||||
import { privateRoomMachine } from "@/stores/private-room/private-room-machine";
|
||||
} from "@/data/schemas/private-zoom";
|
||||
import { privateZoomMachine } from "@/stores/private-zoom/private-zoom-machine";
|
||||
|
||||
export const ALBUM_ID = "a1b2c3d4-0000-0000-0000-000000000000";
|
||||
export const COVER_URL =
|
||||
"https://dbapi.banlv-ai.com/storage/v1/object/public/elio-schedules/01.jpg";
|
||||
export const TEST_PRIVATE_ROOM_INPUT = {
|
||||
export const TEST_PRIVATE_ZOOM_INPUT = {
|
||||
characterId: DEFAULT_CHARACTER_ID,
|
||||
};
|
||||
|
||||
@@ -65,7 +65,7 @@ export function makeUnlockResponse(
|
||||
});
|
||||
}
|
||||
|
||||
export function createTestPrivateRoomMachine(
|
||||
export function createTestPrivateZoomMachine(
|
||||
options: {
|
||||
loadResponse?: PrivateAlbumsResponse;
|
||||
unlockResponse?: PrivateAlbumUnlockResponse;
|
||||
@@ -74,7 +74,7 @@ export function createTestPrivateRoomMachine(
|
||||
onLoad?: (input: { characterId: string }) => void;
|
||||
} = {},
|
||||
) {
|
||||
return privateRoomMachine.provide({
|
||||
return privateZoomMachine.provide({
|
||||
actors: {
|
||||
loadAlbums: fromPromise<PrivateAlbumsResponse, { characterId: string }>(
|
||||
async ({ input }) => {
|
||||
@@ -91,24 +91,24 @@ export function createTestPrivateRoomMachine(
|
||||
});
|
||||
}
|
||||
|
||||
export async function loadPrivateRoom(
|
||||
options: Parameters<typeof createTestPrivateRoomMachine>[0] = {},
|
||||
export async function loadPrivateZoom(
|
||||
options: Parameters<typeof createTestPrivateZoomMachine>[0] = {},
|
||||
) {
|
||||
const actor = createActor(createTestPrivateRoomMachine(options), {
|
||||
input: TEST_PRIVATE_ROOM_INPUT,
|
||||
const actor = createActor(createTestPrivateZoomMachine(options), {
|
||||
input: TEST_PRIVATE_ZOOM_INPUT,
|
||||
}).start();
|
||||
actor.send({ type: "PrivateRoomInit" });
|
||||
actor.send({ type: "PrivateZoomInit" });
|
||||
await waitFor(actor, (snapshot) => snapshot.matches("ready"));
|
||||
return actor;
|
||||
}
|
||||
|
||||
export async function unlockFirstAlbum(
|
||||
actor: Awaited<ReturnType<typeof loadPrivateRoom>>,
|
||||
actor: Awaited<ReturnType<typeof loadPrivateZoom>>,
|
||||
) {
|
||||
actor.send({
|
||||
type: "PrivateRoomUnlockRequested",
|
||||
type: "PrivateZoomUnlockRequested",
|
||||
albumId: ALBUM_ID,
|
||||
});
|
||||
actor.send({ type: "PrivateRoomUnlockConfirmed" });
|
||||
actor.send({ type: "PrivateZoomUnlockConfirmed" });
|
||||
await waitFor(actor, (snapshot) => snapshot.matches("ready"));
|
||||
}
|
||||
+9
-9
@@ -3,14 +3,14 @@ import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
ALBUM_ID,
|
||||
COVER_URL,
|
||||
loadPrivateRoom,
|
||||
loadPrivateZoom,
|
||||
makeUnlockResponse,
|
||||
unlockFirstAlbum,
|
||||
} from "./private-room-machine.test-utils";
|
||||
} from "./private-zoom-machine.test-utils";
|
||||
|
||||
describe("private room unlock flow", () => {
|
||||
describe("private zoom unlock flow", () => {
|
||||
it("patches album images after a successful unlock", async () => {
|
||||
const actor = await loadPrivateRoom();
|
||||
const actor = await loadPrivateZoom();
|
||||
await unlockFirstAlbum(actor);
|
||||
expect(actor.getSnapshot().context.items[0]).toMatchObject({
|
||||
albumId: ALBUM_ID,
|
||||
@@ -24,7 +24,7 @@ describe("private room unlock flow", () => {
|
||||
});
|
||||
|
||||
it("exposes a paywall request when credits are low", async () => {
|
||||
const actor = await loadPrivateRoom({
|
||||
const actor = await loadPrivateZoom({
|
||||
unlockResponse: makeUnlockResponse({
|
||||
locked: true,
|
||||
unlocked: false,
|
||||
@@ -47,7 +47,7 @@ describe("private room unlock flow", () => {
|
||||
|
||||
it("reloads when expected cost changes", async () => {
|
||||
let loadCount = 0;
|
||||
const actor = await loadPrivateRoom({
|
||||
const actor = await loadPrivateZoom({
|
||||
onLoad: () => {
|
||||
loadCount += 1;
|
||||
},
|
||||
@@ -66,7 +66,7 @@ describe("private room unlock flow", () => {
|
||||
});
|
||||
|
||||
it("removes an album that no longer exists", async () => {
|
||||
const actor = await loadPrivateRoom({
|
||||
const actor = await loadPrivateZoom({
|
||||
unlockResponse: makeUnlockResponse({
|
||||
locked: true,
|
||||
unlocked: false,
|
||||
@@ -80,7 +80,7 @@ describe("private room unlock flow", () => {
|
||||
});
|
||||
|
||||
it("keeps refunded persistence failures locked", async () => {
|
||||
const actor = await loadPrivateRoom({
|
||||
const actor = await loadPrivateZoom({
|
||||
unlockResponse: makeUnlockResponse({
|
||||
locked: true,
|
||||
unlocked: false,
|
||||
@@ -99,7 +99,7 @@ describe("private room unlock flow", () => {
|
||||
});
|
||||
|
||||
it("returns to ready when the unlock actor rejects", async () => {
|
||||
const actor = await loadPrivateRoom({
|
||||
const actor = await loadPrivateZoom({
|
||||
unlockError: new Error("unlock unavailable"),
|
||||
});
|
||||
await unlockFirstAlbum(actor);
|
||||
@@ -3,15 +3,15 @@ import {
|
||||
PrivateAlbumSchema,
|
||||
type PrivateAlbumsResponse,
|
||||
type PrivateAlbumUnlockResponse,
|
||||
} from "@/data/schemas/private-room";
|
||||
} from "@/data/schemas/private-zoom";
|
||||
|
||||
import type { PrivateRoomState } from "../private-room-state";
|
||||
import type { PrivateZoomState } from "../private-zoom-state";
|
||||
|
||||
export const PRIVATE_ROOM_PAGE_SIZE = 20;
|
||||
export const PRIVATE_ZOOM_PAGE_SIZE = 20;
|
||||
|
||||
export function applyAlbumsResponse(
|
||||
response: PrivateAlbumsResponse,
|
||||
): Partial<PrivateRoomState> {
|
||||
): Partial<PrivateZoomState> {
|
||||
return {
|
||||
items: response.items,
|
||||
creditBalance: response.creditBalance,
|
||||
@@ -20,7 +20,7 @@ export function applyAlbumsResponse(
|
||||
}
|
||||
|
||||
export function getPendingAlbum(
|
||||
context: PrivateRoomState,
|
||||
context: PrivateZoomState,
|
||||
): PrivateAlbum | null {
|
||||
if (!context.pendingConfirmAlbumId) return null;
|
||||
return (
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { PrivateAlbumUnlockResponse } from "@/data/schemas/private-room";
|
||||
import type { PrivateAlbumUnlockResponse } from "@/data/schemas/private-zoom";
|
||||
|
||||
import type { PrivateRoomUnlockPaywallRequest } from "../private-room-state";
|
||||
import type { PrivateZoomUnlockPaywallRequest } from "../private-zoom-state";
|
||||
|
||||
export function toPrivateRoomErrorMessage(error: unknown): string {
|
||||
export function toPrivateZoomErrorMessage(error: unknown): string {
|
||||
if (error instanceof Error && error.message) return error.message;
|
||||
return "Private room is temporarily unavailable. Please try again.";
|
||||
return "Private Zoom is temporarily unavailable. Please try again.";
|
||||
}
|
||||
|
||||
export function toUnlockErrorMessage(
|
||||
@@ -30,7 +30,7 @@ export function toUnlockErrorMessage(
|
||||
|
||||
export function toPaywallRequest(
|
||||
response: PrivateAlbumUnlockResponse,
|
||||
): PrivateRoomUnlockPaywallRequest {
|
||||
): PrivateZoomUnlockPaywallRequest {
|
||||
return {
|
||||
albumId: response.albumId,
|
||||
reason: response.reason,
|
||||
@@ -0,0 +1,7 @@
|
||||
/** @file Private Zoom store barrel. */
|
||||
|
||||
export * from "./private-zoom-context";
|
||||
export * from "./private-zoom-events";
|
||||
export * from "./helper";
|
||||
export * from "./private-zoom-machine";
|
||||
export * from "./private-zoom-state";
|
||||
+6
-6
@@ -3,19 +3,19 @@ import { fromPromise } from "xstate";
|
||||
import type {
|
||||
PrivateAlbumsResponse,
|
||||
PrivateAlbumUnlockResponse,
|
||||
} from "@/data/schemas/private-room";
|
||||
import { getPrivateRoomRepository } from "@/data/repositories/private_room_repository";
|
||||
} from "@/data/schemas/private-zoom";
|
||||
import { getPrivateZoomRepository } from "@/data/repositories/private_zoom_repository";
|
||||
import { Result } from "@/utils/result";
|
||||
|
||||
import { PRIVATE_ROOM_PAGE_SIZE } from "../../helper/albums";
|
||||
import { PRIVATE_ZOOM_PAGE_SIZE } from "../../helper/albums";
|
||||
|
||||
export const loadPrivateAlbumsActor =
|
||||
fromPromise<PrivateAlbumsResponse, { characterId: string }>(async ({
|
||||
input,
|
||||
}) => {
|
||||
const result = await getPrivateRoomRepository().getAlbums({
|
||||
const result = await getPrivateZoomRepository().getAlbums({
|
||||
characterId: input.characterId,
|
||||
limit: PRIVATE_ROOM_PAGE_SIZE,
|
||||
limit: PRIVATE_ZOOM_PAGE_SIZE,
|
||||
});
|
||||
if (Result.isErr(result)) throw result.error;
|
||||
return result.data;
|
||||
@@ -25,7 +25,7 @@ export const unlockPrivateAlbumActor = fromPromise<
|
||||
PrivateAlbumUnlockResponse,
|
||||
{ albumId: string; expectedCost: number }
|
||||
>(async ({ input }) => {
|
||||
const result = await getPrivateRoomRepository().unlockAlbum(
|
||||
const result = await getPrivateZoomRepository().unlockAlbum(
|
||||
input.albumId,
|
||||
input.expectedCost,
|
||||
);
|
||||
+12
-12
@@ -1,43 +1,43 @@
|
||||
import type { DoneActorEvent, ErrorActorEvent } from "xstate";
|
||||
|
||||
import type { PrivateAlbumsResponse } from "@/data/schemas/private-room";
|
||||
import type { PrivateAlbumsResponse } from "@/data/schemas/private-zoom";
|
||||
|
||||
import { applyAlbumsResponse } from "../helper/albums";
|
||||
import { toPrivateRoomErrorMessage } from "../helper/unlock";
|
||||
import { toPrivateZoomErrorMessage } from "../helper/unlock";
|
||||
import {
|
||||
basePrivateRoomMachineSetup,
|
||||
createPrivateRoomActorActionSetup,
|
||||
basePrivateZoomMachineSetup,
|
||||
createPrivateZoomActorActionSetup,
|
||||
} from "./setup";
|
||||
|
||||
const clearAlbumLoadStateAction = basePrivateRoomMachineSetup.assign({
|
||||
const clearAlbumLoadStateAction = basePrivateZoomMachineSetup.assign({
|
||||
errorMessage: null,
|
||||
unlockPaywallRequest: null,
|
||||
});
|
||||
|
||||
export const albumMachineSetup = basePrivateRoomMachineSetup.extend({
|
||||
export const albumMachineSetup = basePrivateZoomMachineSetup.extend({
|
||||
actions: {
|
||||
clearAlbumLoadState: clearAlbumLoadStateAction,
|
||||
},
|
||||
});
|
||||
|
||||
const albumsDoneSetup =
|
||||
createPrivateRoomActorActionSetup<
|
||||
createPrivateZoomActorActionSetup<
|
||||
DoneActorEvent<PrivateAlbumsResponse>
|
||||
>();
|
||||
const albumErrorSetup =
|
||||
createPrivateRoomActorActionSetup<ErrorActorEvent>();
|
||||
createPrivateZoomActorActionSetup<ErrorActorEvent>();
|
||||
|
||||
const applyAlbumsResponseAction = albumsDoneSetup.assign(({ event }) =>
|
||||
applyAlbumsResponse(event.output),
|
||||
);
|
||||
|
||||
const applyAlbumLoadErrorAction = albumErrorSetup.assign(({ event }) => ({
|
||||
errorMessage: toPrivateRoomErrorMessage(event.error),
|
||||
errorMessage: toPrivateZoomErrorMessage(event.error),
|
||||
}));
|
||||
|
||||
export const idleState = albumMachineSetup.createStateConfig({
|
||||
on: {
|
||||
PrivateRoomInit: "loading",
|
||||
PrivateZoomInit: "loading",
|
||||
},
|
||||
});
|
||||
|
||||
@@ -59,7 +59,7 @@ export const loadingState = albumMachineSetup.createStateConfig({
|
||||
|
||||
export const failedState = albumMachineSetup.createStateConfig({
|
||||
on: {
|
||||
PrivateRoomInit: "loading",
|
||||
PrivateRoomRefresh: "loading",
|
||||
PrivateZoomInit: "loading",
|
||||
PrivateZoomRefresh: "loading",
|
||||
},
|
||||
});
|
||||
+14
-14
@@ -1,22 +1,22 @@
|
||||
import { setup, type ActionFunction, type EventObject } from "xstate";
|
||||
|
||||
import { getPendingAlbum } from "../helper/albums";
|
||||
import type { PrivateRoomEvent } from "../private-room-events";
|
||||
import type { PrivateRoomState } from "../private-room-state";
|
||||
import type { PrivateZoomEvent } from "../private-zoom-events";
|
||||
import type { PrivateZoomState } from "../private-zoom-state";
|
||||
import {
|
||||
loadPrivateAlbumsActor,
|
||||
unlockPrivateAlbumActor,
|
||||
} from "./actors/albums";
|
||||
|
||||
export interface PrivateRoomMachineInput {
|
||||
export interface PrivateZoomMachineInput {
|
||||
characterId: string;
|
||||
}
|
||||
|
||||
export const basePrivateRoomMachineSetup = setup({
|
||||
export const basePrivateZoomMachineSetup = setup({
|
||||
types: {
|
||||
context: {} as PrivateRoomState,
|
||||
events: {} as PrivateRoomEvent,
|
||||
input: {} as PrivateRoomMachineInput,
|
||||
context: {} as PrivateZoomState,
|
||||
events: {} as PrivateZoomEvent,
|
||||
input: {} as PrivateZoomMachineInput,
|
||||
},
|
||||
actors: {
|
||||
loadAlbums: loadPrivateAlbumsActor,
|
||||
@@ -27,10 +27,10 @@ export const basePrivateRoomMachineSetup = setup({
|
||||
},
|
||||
});
|
||||
|
||||
type PrivateRoomActorAction<TEvent extends EventObject> = ActionFunction<
|
||||
PrivateRoomState,
|
||||
type PrivateZoomActorAction<TEvent extends EventObject> = ActionFunction<
|
||||
PrivateZoomState,
|
||||
TEvent,
|
||||
PrivateRoomEvent,
|
||||
PrivateZoomEvent,
|
||||
undefined,
|
||||
never,
|
||||
never,
|
||||
@@ -39,22 +39,22 @@ type PrivateRoomActorAction<TEvent extends EventObject> = ActionFunction<
|
||||
never
|
||||
>;
|
||||
|
||||
export function createPrivateRoomActorActionSetup<
|
||||
export function createPrivateZoomActorActionSetup<
|
||||
TEvent extends EventObject,
|
||||
>() {
|
||||
const actorActionSetup = setup({
|
||||
types: {
|
||||
context: {} as PrivateRoomState,
|
||||
context: {} as PrivateZoomState,
|
||||
events: {} as TEvent,
|
||||
},
|
||||
});
|
||||
return {
|
||||
assign(
|
||||
assignment: Parameters<typeof actorActionSetup.assign>[0],
|
||||
): PrivateRoomActorAction<TEvent> {
|
||||
): PrivateZoomActorAction<TEvent> {
|
||||
return actorActionSetup.assign(
|
||||
assignment,
|
||||
) as unknown as PrivateRoomActorAction<TEvent>;
|
||||
) as unknown as PrivateZoomActorAction<TEvent>;
|
||||
},
|
||||
};
|
||||
}
|
||||
+18
-18
@@ -1,6 +1,6 @@
|
||||
import type { DoneActorEvent, ErrorActorEvent } from "xstate";
|
||||
|
||||
import type { PrivateAlbumUnlockResponse } from "@/data/schemas/private-room";
|
||||
import type { PrivateAlbumUnlockResponse } from "@/data/schemas/private-zoom";
|
||||
|
||||
import {
|
||||
getPendingAlbum,
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
} from "../helper/albums";
|
||||
import {
|
||||
toPaywallRequest,
|
||||
toPrivateRoomErrorMessage,
|
||||
toPrivateZoomErrorMessage,
|
||||
toUnlockErrorMessage,
|
||||
} from "../helper/unlock";
|
||||
import {
|
||||
@@ -18,10 +18,10 @@ import {
|
||||
idleState,
|
||||
loadingState,
|
||||
} from "./album-flow";
|
||||
import { createPrivateRoomActorActionSetup } from "./setup";
|
||||
import { createPrivateZoomActorActionSetup } from "./setup";
|
||||
|
||||
const requestUnlockAction = albumMachineSetup.assign(({ event }) =>
|
||||
event.type === "PrivateRoomUnlockRequested"
|
||||
event.type === "PrivateZoomUnlockRequested"
|
||||
? {
|
||||
pendingConfirmAlbumId: event.albumId,
|
||||
unlockErrorMessage: null,
|
||||
@@ -44,7 +44,7 @@ const beginUnlockAction = albumMachineSetup.assign(({ context }) => ({
|
||||
unlockPaywallRequest: null,
|
||||
}));
|
||||
|
||||
export const privateRoomMachineSetup = albumMachineSetup.extend({
|
||||
export const privateZoomMachineSetup = albumMachineSetup.extend({
|
||||
actions: {
|
||||
requestUnlock: requestUnlockAction,
|
||||
cancelUnlock: cancelUnlockAction,
|
||||
@@ -54,11 +54,11 @@ export const privateRoomMachineSetup = albumMachineSetup.extend({
|
||||
});
|
||||
|
||||
const unlockDoneSetup =
|
||||
createPrivateRoomActorActionSetup<
|
||||
createPrivateZoomActorActionSetup<
|
||||
DoneActorEvent<PrivateAlbumUnlockResponse>
|
||||
>();
|
||||
const unlockErrorSetup =
|
||||
createPrivateRoomActorActionSetup<ErrorActorEvent>();
|
||||
createPrivateZoomActorActionSetup<ErrorActorEvent>();
|
||||
|
||||
const applyUnlockSuccessAction = unlockDoneSetup.assign(
|
||||
({ context, event }) => ({
|
||||
@@ -111,26 +111,26 @@ const applyUnlockFailureAction = unlockDoneSetup.assign(
|
||||
const applyUnlockActorErrorAction = unlockErrorSetup.assign(({ event }) => ({
|
||||
pendingConfirmAlbumId: null,
|
||||
unlockingAlbumId: null,
|
||||
unlockErrorMessage: toPrivateRoomErrorMessage(event.error),
|
||||
unlockErrorMessage: toPrivateZoomErrorMessage(event.error),
|
||||
}));
|
||||
|
||||
const readyState = privateRoomMachineSetup.createStateConfig({
|
||||
const readyState = privateZoomMachineSetup.createStateConfig({
|
||||
on: {
|
||||
PrivateRoomInit: "loading",
|
||||
PrivateRoomRefresh: "loading",
|
||||
PrivateRoomUnlockRequested: { actions: "requestUnlock" },
|
||||
PrivateRoomUnlockCancelled: { actions: "cancelUnlock" },
|
||||
PrivateRoomUnlockConfirmed: [
|
||||
PrivateZoomInit: "loading",
|
||||
PrivateZoomRefresh: "loading",
|
||||
PrivateZoomUnlockRequested: { actions: "requestUnlock" },
|
||||
PrivateZoomUnlockCancelled: { actions: "cancelUnlock" },
|
||||
PrivateZoomUnlockConfirmed: [
|
||||
{
|
||||
guard: "hasPendingAlbum",
|
||||
target: "unlocking",
|
||||
},
|
||||
],
|
||||
PrivateRoomUnlockPaywallConsumed: { actions: "clearUnlockPaywall" },
|
||||
PrivateZoomUnlockPaywallConsumed: { actions: "clearUnlockPaywall" },
|
||||
},
|
||||
});
|
||||
|
||||
const unlockingState = privateRoomMachineSetup.createStateConfig({
|
||||
const unlockingState = privateZoomMachineSetup.createStateConfig({
|
||||
entry: "beginUnlock",
|
||||
invoke: {
|
||||
src: "unlockAlbum",
|
||||
@@ -175,8 +175,8 @@ const unlockingState = privateRoomMachineSetup.createStateConfig({
|
||||
},
|
||||
});
|
||||
|
||||
export const privateRoomRootStateConfig =
|
||||
privateRoomMachineSetup.createStateConfig({
|
||||
export const privateZoomRootStateConfig =
|
||||
privateZoomMachineSetup.createStateConfig({
|
||||
initial: "idle",
|
||||
states: {
|
||||
idle: idleState,
|
||||
+24
-24
@@ -4,14 +4,14 @@ import type { Dispatch, ReactNode } from "react";
|
||||
import { createActorContext, shallowEqual } from "@xstate/react";
|
||||
import type { SnapshotFrom } from "xstate";
|
||||
|
||||
import { privateRoomMachine } from "./private-room-machine";
|
||||
import { privateZoomMachine } from "./private-zoom-machine";
|
||||
import type {
|
||||
PrivateRoomEvent,
|
||||
PrivateRoomState as MachineContext,
|
||||
} from "./private-room-machine";
|
||||
PrivateZoomEvent,
|
||||
PrivateZoomState as MachineContext,
|
||||
} from "./private-zoom-machine";
|
||||
|
||||
/** Route UI reads this projection instead of the Private Room snapshot. */
|
||||
export interface PrivateRoomContextState {
|
||||
/** Route UI reads this projection instead of the Private Zoom snapshot. */
|
||||
export interface PrivateZoomContextState {
|
||||
characterId: string;
|
||||
status: string;
|
||||
items: MachineContext["items"];
|
||||
@@ -26,45 +26,45 @@ export interface PrivateRoomContextState {
|
||||
isUnlocking: boolean;
|
||||
}
|
||||
|
||||
type PrivateRoomSnapshot = SnapshotFrom<typeof privateRoomMachine>;
|
||||
type PrivateRoomSelector<T> = (snapshot: PrivateRoomSnapshot) => T;
|
||||
type PrivateZoomSnapshot = SnapshotFrom<typeof privateZoomMachine>;
|
||||
type PrivateZoomSelector<T> = (snapshot: PrivateZoomSnapshot) => T;
|
||||
|
||||
const PrivateRoomActorContext = createActorContext(privateRoomMachine);
|
||||
const PrivateZoomActorContext = createActorContext(privateZoomMachine);
|
||||
|
||||
export interface PrivateRoomProviderProps {
|
||||
export interface PrivateZoomProviderProps {
|
||||
children: ReactNode;
|
||||
characterId: string;
|
||||
}
|
||||
|
||||
export function PrivateRoomProvider({
|
||||
export function PrivateZoomProvider({
|
||||
children,
|
||||
characterId,
|
||||
}: PrivateRoomProviderProps) {
|
||||
}: PrivateZoomProviderProps) {
|
||||
return (
|
||||
<PrivateRoomActorContext.Provider options={{ input: { characterId } }}>
|
||||
<PrivateZoomActorContext.Provider options={{ input: { characterId } }}>
|
||||
{children}
|
||||
</PrivateRoomActorContext.Provider>
|
||||
</PrivateZoomActorContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function usePrivateRoomState(): PrivateRoomContextState {
|
||||
return usePrivateRoomSelector(selectPrivateRoomState, shallowEqual);
|
||||
export function usePrivateZoomState(): PrivateZoomContextState {
|
||||
return usePrivateZoomSelector(selectPrivateZoomState, shallowEqual);
|
||||
}
|
||||
|
||||
export function usePrivateRoomDispatch(): Dispatch<PrivateRoomEvent> {
|
||||
return PrivateRoomActorContext.useActorRef().send;
|
||||
export function usePrivateZoomDispatch(): Dispatch<PrivateZoomEvent> {
|
||||
return PrivateZoomActorContext.useActorRef().send;
|
||||
}
|
||||
|
||||
export function usePrivateRoomSelector<T>(
|
||||
selector: PrivateRoomSelector<T>,
|
||||
export function usePrivateZoomSelector<T>(
|
||||
selector: PrivateZoomSelector<T>,
|
||||
compare?: (previous: T, next: T) => boolean,
|
||||
): T {
|
||||
return PrivateRoomActorContext.useSelector(selector, compare);
|
||||
return PrivateZoomActorContext.useSelector(selector, compare);
|
||||
}
|
||||
|
||||
function selectPrivateRoomState(
|
||||
state: PrivateRoomSnapshot,
|
||||
): PrivateRoomContextState {
|
||||
function selectPrivateZoomState(
|
||||
state: PrivateZoomSnapshot,
|
||||
): PrivateZoomContextState {
|
||||
return {
|
||||
characterId: state.context.characterId,
|
||||
status: String(state.value),
|
||||
@@ -0,0 +1,8 @@
|
||||
/** Public events for one character-scoped Private Zoom actor. */
|
||||
export type PrivateZoomEvent =
|
||||
| { type: "PrivateZoomInit" }
|
||||
| { type: "PrivateZoomRefresh" }
|
||||
| { type: "PrivateZoomUnlockRequested"; albumId: string }
|
||||
| { type: "PrivateZoomUnlockConfirmed" }
|
||||
| { type: "PrivateZoomUnlockCancelled" }
|
||||
| { type: "PrivateZoomUnlockPaywallConsumed" };
|
||||
@@ -0,0 +1,18 @@
|
||||
import {
|
||||
privateZoomMachineSetup,
|
||||
privateZoomRootStateConfig,
|
||||
} from "./machine/unlock-flow";
|
||||
import { createInitialPrivateZoomState } from "./private-zoom-state";
|
||||
|
||||
export type { PrivateZoomEvent } from "./private-zoom-events";
|
||||
export type { PrivateZoomState } from "./private-zoom-state";
|
||||
export { initialState } from "./private-zoom-state";
|
||||
|
||||
export const privateZoomMachine = privateZoomMachineSetup.createMachine({
|
||||
id: "privateZoom",
|
||||
context: ({ input }) =>
|
||||
createInitialPrivateZoomState(input.characterId),
|
||||
...privateZoomRootStateConfig,
|
||||
});
|
||||
|
||||
export type PrivateZoomMachine = typeof privateZoomMachine;
|
||||
+7
-7
@@ -1,7 +1,7 @@
|
||||
import { DEFAULT_CHARACTER_ID } from "@/data/constants/character";
|
||||
import type { PrivateAlbum } from "@/data/schemas/private-room";
|
||||
import type { PrivateAlbum } from "@/data/schemas/private-zoom";
|
||||
|
||||
export interface PrivateRoomUnlockPaywallRequest {
|
||||
export interface PrivateZoomUnlockPaywallRequest {
|
||||
albumId: string;
|
||||
reason: string;
|
||||
requiredCredits: number;
|
||||
@@ -9,7 +9,7 @@ export interface PrivateRoomUnlockPaywallRequest {
|
||||
shortfallCredits: number;
|
||||
}
|
||||
|
||||
export interface PrivateRoomState {
|
||||
export interface PrivateZoomState {
|
||||
characterId: string;
|
||||
items: readonly PrivateAlbum[];
|
||||
creditBalance: number;
|
||||
@@ -17,13 +17,13 @@ export interface PrivateRoomState {
|
||||
unlockingAlbumId: string | null;
|
||||
unlockErrorMessage: string | null;
|
||||
pendingConfirmAlbumId: string | null;
|
||||
unlockPaywallRequest: PrivateRoomUnlockPaywallRequest | null;
|
||||
unlockPaywallRequest: PrivateZoomUnlockPaywallRequest | null;
|
||||
unlockSuccessNonce: number;
|
||||
}
|
||||
|
||||
export function createInitialPrivateRoomState(
|
||||
export function createInitialPrivateZoomState(
|
||||
characterId: string,
|
||||
): PrivateRoomState {
|
||||
): PrivateZoomState {
|
||||
return {
|
||||
characterId,
|
||||
items: [],
|
||||
@@ -37,4 +37,4 @@ export function createInitialPrivateRoomState(
|
||||
};
|
||||
}
|
||||
|
||||
export const initialState = createInitialPrivateRoomState(DEFAULT_CHARACTER_ID);
|
||||
export const initialState = createInitialPrivateZoomState(DEFAULT_CHARACTER_ID);
|
||||
Reference in New Issue
Block a user