refactor(private-zone): use canonical product name
Docker Image / Build and Push Docker Image (push) Successful in 2m7s

This commit is contained in:
Codex
2026-07-23 10:53:41 +08:00
parent 30122a44db
commit 0357fbcaff
105 changed files with 578 additions and 578 deletions
@@ -85,8 +85,8 @@ describe("shared Tailwind components", () => {
<CharacterAvatar
src="/images/avatar/elio.png"
alt="Elio Silvestri"
actionLabel="Open Elio's private zoom"
analyticsKey="chat.open_private_zoom_from_avatar"
actionLabel="Open Elio's private zone"
analyticsKey="chat.open_private_zone_from_avatar"
onClick={() => undefined}
/>,
);
@@ -100,10 +100,10 @@ describe("shared Tailwind components", () => {
expect(characterHtml).toContain('<button type="button"');
expect(characterHtml).toContain(
'aria-label="Open Elio&#x27;s private zoom"',
'aria-label="Open Elio&#x27;s private zone"',
);
expect(characterHtml).toContain(
'data-analytics-key="chat.open_private_zoom_from_avatar"',
'data-analytics-key="chat.open_private_zone_from_avatar"',
);
expect(userHtml).toContain('<button type="button"');
expect(userHtml).toContain('aria-label="Open profile"');
@@ -79,19 +79,19 @@ describe("core Tailwind components", () => {
<AppBottomNav
activeItem="chat"
variant="dark"
privateZoomLabel="Maya Private Zoom"
privateZoneLabel="Maya Private Zone"
onChatClick={() => undefined}
onPrivateZoomClick={() => undefined}
onPrivateZoneClick={() => undefined}
onMenuClick={() => undefined}
/>,
);
const privateZoomHtml = renderToStaticMarkup(
const privateZoneHtml = renderToStaticMarkup(
<AppBottomNav
activeItem="privateZoom"
activeItem="privateZone"
variant="warm"
privateZoomLabel="Maya Private Zoom"
privateZoneLabel="Maya Private Zone"
onChatClick={() => undefined}
onPrivateZoomClick={() => undefined}
onPrivateZoneClick={() => undefined}
onMenuClick={() => undefined}
/>,
);
@@ -101,13 +101,13 @@ describe("core Tailwind components", () => {
expect(chatHtml).toMatch(
/<button[^>]*aria-current="page"[^>]*>.*?<span>Chat<\/span>/,
);
expect(privateZoomHtml).toMatch(/class="[^"]*warm[^"]*"/);
expect(privateZoomHtml).toMatch(
/<button[^>]*aria-current="page"[^>]*>.*?<span>Maya Private Zoom<\/span>/,
expect(privateZoneHtml).toMatch(/class="[^"]*warm[^"]*"/);
expect(privateZoneHtml).toMatch(
/<button[^>]*aria-current="page"[^>]*>.*?<span>Maya Private Zone<\/span>/,
);
expect(chatHtml).toContain('data-analytics-key="navigation.chat"');
expect(privateZoomHtml).toContain(
'data-analytics-key="navigation.private_zoom"',
expect(privateZoneHtml).toContain(
'data-analytics-key="navigation.private_zone"',
);
expect(chatHtml).toContain('data-analytics-key="navigation.menu"');
expect(chatHtml).toContain("<span>Menu</span>");
+11 -11
View File
@@ -4,24 +4,24 @@ import { Camera, Menu as MenuIcon, MessageCircle } from "lucide-react";
import styles from "./app-bottom-nav.module.css";
export type AppBottomNavItem = "chat" | "privateZoom" | "menu";
export type AppBottomNavItem = "chat" | "privateZone" | "menu";
export type AppBottomNavVariant = "warm" | "dark";
export interface AppBottomNavProps {
activeItem?: AppBottomNavItem | null;
variant?: AppBottomNavVariant;
privateZoomLabel: string;
privateZoneLabel: string;
onChatClick: () => void;
onPrivateZoomClick: () => void;
onPrivateZoneClick: () => void;
onMenuClick: () => void;
}
export function AppBottomNav({
activeItem = null,
variant = "warm",
privateZoomLabel,
privateZoneLabel,
onChatClick,
onPrivateZoomClick,
onPrivateZoneClick,
onMenuClick,
}: AppBottomNavProps) {
return (
@@ -39,14 +39,14 @@ export function AppBottomNav({
</button>
<button
type="button"
data-analytics-key="navigation.private_zoom"
data-analytics-label="Private Zoom navigation"
className={getButtonClass(activeItem === "privateZoom")}
aria-current={activeItem === "privateZoom" ? "page" : undefined}
onClick={onPrivateZoomClick}
data-analytics-key="navigation.private_zone"
data-analytics-label="Private Zone navigation"
className={getButtonClass(activeItem === "privateZone")}
aria-current={activeItem === "privateZone" ? "page" : undefined}
onClick={onPrivateZoneClick}
>
<Camera size={20} aria-hidden="true" />
<span>{privateZoomLabel}</span>
<span>{privateZoneLabel}</span>
</button>
<button
type="button"
@@ -2,9 +2,9 @@ import type { ReactNode } from "react";
import { notFound } from "next/navigation";
import { getCharacterBySlug } from "@/data/constants/character";
import { PrivateZoomRouteProvider } from "@/providers/private-zoom-route-provider";
import { PrivateZoneRouteProvider } from "@/providers/private-zone-route-provider";
export default async function CharacterPrivateZoomLayout({
export default async function CharacterPrivateZoneLayout({
children,
params,
}: {
@@ -13,11 +13,11 @@ export default async function CharacterPrivateZoomLayout({
}) {
const { characterSlug } = await params;
const character = getCharacterBySlug(characterSlug);
if (!character?.capabilities.privateZoom) notFound();
if (!character?.capabilities.privateZone) notFound();
return (
<PrivateZoomRouteProvider characterId={character.id}>
<PrivateZoneRouteProvider characterId={character.id}>
{children}
</PrivateZoomRouteProvider>
</PrivateZoneRouteProvider>
);
}
@@ -0,0 +1,5 @@
import { PrivateZoneScreen } from "@/app/private-zone/private-zone-screen";
export default function CharacterPrivateZonePage() {
return <PrivateZoneScreen />;
}
@@ -1,5 +0,0 @@
import { PrivateZoomScreen } from "@/app/private-zoom/private-zoom-screen";
export default function CharacterPrivateZoomPage() {
return <PrivateZoomScreen />;
}
+3 -3
View File
@@ -246,8 +246,8 @@ export function ChatScreen() {
);
}
function handleOpenCharacterPrivateZoom(): void {
router.push(characterRoutes.privateZoom);
function handleOpenCharacterPrivateZone(): void {
router.push(characterRoutes.privateZone);
}
return (
@@ -297,7 +297,7 @@ export function ChatScreen() {
onUnlockImageMessage={handleUnlockImageMessage}
onOpenImage={handleOpenImage}
onUserAvatarClick={handleOpenUserProfile}
onCharacterAvatarClick={handleOpenCharacterPrivateZoom}
onCharacterAvatarClick={handleOpenCharacterPrivateZone}
onLoadMoreHistory={() => {
chatDispatch({ type: "ChatLoadMoreHistoryRequested" });
}}
@@ -62,10 +62,10 @@ describe("chat Tailwind components", () => {
expect(aiHtml).toContain('<button type="button"');
expect(aiHtml).toContain(
'data-analytics-key="chat.open_private_zoom_from_avatar"',
'data-analytics-key="chat.open_private_zone_from_avatar"',
);
expect(aiHtml).toContain(
'aria-label="Open Elio Silvestri&#x27;s private zoom"',
'aria-label="Open Elio Silvestri&#x27;s private zone"',
);
expect(userHtml).toContain('<button type="button"');
expect(userHtml).toContain(
+2 -2
View File
@@ -29,8 +29,8 @@ export function MessageAvatar({
imageSize={43}
priority
className={AVATAR_CLASS_NAME}
actionLabel={`Open ${character.displayName}'s private zoom`}
analyticsKey="chat.open_private_zoom_from_avatar"
actionLabel={`Open ${character.displayName}'s private zone`}
analyticsKey="chat.open_private_zone_from_avatar"
onClick={onClick}
/>
);
@@ -2,7 +2,7 @@ import { describe, expect, it } from "vitest";
import {
buildPrivateAlbumGalleryUrl,
buildPrivateZoomWithoutGalleryUrl,
buildPrivateZoneWithoutGalleryUrl,
getPrivateAlbumGalleryState,
} from "../private-album-gallery-url";
@@ -11,7 +11,7 @@ describe("private album gallery URL", () => {
const url = buildPrivateAlbumGalleryUrl("album:91", 3);
const params = new URL(url, "https://cozsweet.test").searchParams;
expect(url).toBe("/private-zoom?album=album%3A91&image=3");
expect(url).toBe("/private-zone?album=album%3A91&image=3");
expect(getPrivateAlbumGalleryState(params)).toEqual({
albumId: "album:91",
imageIndex: 3,
@@ -29,9 +29,9 @@ describe("private album gallery URL", () => {
it("removes gallery params without dropping other query values", () => {
expect(
buildPrivateZoomWithoutGalleryUrl(
buildPrivateZoneWithoutGalleryUrl(
new URLSearchParams("album=album-1&image=2&source=external"),
),
).toBe("/private-zoom?source=external");
).toBe("/private-zone?source=external");
});
});
@@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest";
import { PrivateAlbumSchema } from "@/data/schemas/private-zoom";
import { PrivateAlbumSchema } from "@/data/schemas/private-zone";
import {
findPrivateAlbumDisplayImage,
@@ -6,10 +6,10 @@ import { getCharacterBySlug } from "@/data/constants/character";
import type { LoginStatus } from "@/data/schemas/auth";
import { CharacterProvider } from "@/providers/character-provider";
import { getCharacterRoutes } from "@/router/routes";
import type { PrivateZoomEvent } from "@/stores/private-zoom";
import type { PrivateZoomUnlockPaywallRequest } from "@/stores/private-zoom/private-zoom-state";
import type { PrivateZoneEvent } from "@/stores/private-zone";
import type { PrivateZoneUnlockPaywallRequest } from "@/stores/private-zone/private-zone-state";
import { usePrivateZoomUnlockPaywallNavigation } from "../use-private-zoom-flow";
import { usePrivateZoneUnlockPaywallNavigation } from "../use-private-zone-flow";
const mocks = vi.hoisted(() => ({
openAuth: vi.fn(),
@@ -30,7 +30,7 @@ vi.mock("@/lib/analytics", () => ({
},
}));
const unlockPaywallRequest: PrivateZoomUnlockPaywallRequest = {
const unlockPaywallRequest: PrivateZoneUnlockPaywallRequest = {
albumId: "album-1",
reason: "insufficient_credits",
requiredCredits: 10,
@@ -38,7 +38,7 @@ const unlockPaywallRequest: PrivateZoomUnlockPaywallRequest = {
shortfallCredits: 7,
};
describe("Private Zoom paywall navigation", () => {
describe("Private Zone paywall navigation", () => {
let container: HTMLDivElement;
let root: Root;
@@ -58,31 +58,31 @@ describe("Private Zoom paywall navigation", () => {
container.remove();
});
it("opens top up with a private zoom return target", () => {
const roomDispatch = vi.fn<Dispatch<PrivateZoomEvent>>();
it("opens top up with a private zone return target", () => {
const roomDispatch = vi.fn<Dispatch<PrivateZoneEvent>>();
renderHarness(root, "email", roomDispatch);
expect(mocks.openSubscription).toHaveBeenCalledWith({
type: "topup",
returnTo: "private-zoom",
returnTo: "private-zone",
analytics: {
entryPoint: "private_album_unlock",
triggerReason: "insufficient_credits",
},
});
expect(roomDispatch).toHaveBeenCalledWith({
type: "PrivateZoomUnlockPaywallConsumed",
type: "PrivateZoneUnlockPaywallConsumed",
});
});
it("keeps guest users on the private zoom auth return path", () => {
const roomDispatch = vi.fn<Dispatch<PrivateZoomEvent>>();
it("keeps guest users on the private zone auth return path", () => {
const roomDispatch = vi.fn<Dispatch<PrivateZoneEvent>>();
renderHarness(root, "guest", roomDispatch);
expect(mocks.openAuth).toHaveBeenCalledWith(
getCharacterRoutes("maya").privateZoom,
getCharacterRoutes("maya").privateZone,
);
expect(mocks.openSubscription).not.toHaveBeenCalled();
});
@@ -91,7 +91,7 @@ describe("Private Zoom paywall navigation", () => {
function renderHarness(
root: Root,
loginStatus: LoginStatus,
roomDispatch: Dispatch<PrivateZoomEvent>,
roomDispatch: Dispatch<PrivateZoneEvent>,
): void {
const character = getCharacterBySlug("maya");
if (!character) throw new Error("Missing Maya character fixture");
@@ -109,9 +109,9 @@ function Harness({
roomDispatch,
}: {
loginStatus: LoginStatus;
roomDispatch: Dispatch<PrivateZoomEvent>;
roomDispatch: Dispatch<PrivateZoneEvent>;
}) {
usePrivateZoomUnlockPaywallNavigation({
usePrivateZoneUnlockPaywallNavigation({
loginStatus,
roomDispatch,
unlockPaywallRequest,
@@ -1,19 +1,19 @@
import { describe, expect, it } from "vitest";
import { isPrivateZoomAuthRequired } from "../use-private-zoom-flow";
import { isPrivateZoneAuthRequired } from "../use-private-zone-flow";
describe("isPrivateZoomAuthRequired", () => {
describe("isPrivateZoneAuthRequired", () => {
it.each(["guest", "notLoggedIn"] as const)(
"requires auth for %s users",
(loginStatus) => {
expect(isPrivateZoomAuthRequired(loginStatus)).toBe(true);
expect(isPrivateZoneAuthRequired(loginStatus)).toBe(true);
},
);
it.each(["email", "facebook", "google"] as const)(
"allows %s users to top up directly",
(loginStatus) => {
expect(isPrivateZoomAuthRequired(loginStatus)).toBe(false);
expect(isPrivateZoneAuthRequired(loginStatus)).toBe(false);
},
);
});
@@ -4,7 +4,7 @@ import { act } from "react";
import { createRoot, type Root } from "react-dom/client";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { PrivateAlbumSchema } from "@/data/schemas/private-zoom";
import { PrivateAlbumSchema } from "@/data/schemas/private-zone";
import { PrivateAlbumCard } from "../private-album-card";
@@ -33,9 +33,9 @@ describe("PrivateAlbumCard interactions", () => {
imageCount: 4,
images: [
{ url: "", locked: false, index: 0 },
{ url: "/images/private-zoom/locked.png", locked: true, index: 1 },
{ url: "/images/private-zoom/photo-2.png", locked: false, index: 2 },
{ url: "/images/private-zoom/photo-3.png", locked: false, index: 3 },
{ url: "/images/private-zone/locked.png", locked: true, index: 1 },
{ url: "/images/private-zone/photo-2.png", locked: false, index: 2 },
{ url: "/images/private-zone/photo-3.png", locked: false, index: 3 },
],
locked: false,
unlocked: true,
@@ -72,7 +72,7 @@ describe("PrivateAlbumCard interactions", () => {
title: "Locked afternoon",
imageCount: 3,
images: [
{ url: "/images/private-zoom/locked.png", locked: true, index: 0 },
{ url: "/images/private-zone/locked.png", locked: true, index: 0 },
],
locked: true,
unlocked: false,
@@ -5,11 +5,11 @@ import {
PrivateAlbumSchema,
type PrivateAlbum,
type PrivateAlbumInput,
} from "@/data/schemas/private-zoom";
} from "@/data/schemas/private-zone";
import { PrivateAlbumCard } from "../private-album-card";
const COVER_URL = "/images/private-zoom/banner/elio.png";
const COVER_URL = "/images/private-zone/banner/elio.png";
function makeAlbum(overrides: Partial<PrivateAlbumInput> = {}): PrivateAlbum {
return PrivateAlbumSchema.parse({
@@ -157,7 +157,7 @@ describe("PrivateAlbumCard", () => {
function makeImages(count: number) {
return Array.from({ length: count }, (_, index) => ({
url: `/images/private-zoom/photo-${index}.png`,
url: `/images/private-zone/photo-${index}.png`,
locked: false,
index,
}));
@@ -4,7 +4,7 @@ import { act } from "react";
import { createRoot, type Root } from "react-dom/client";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { PrivateAlbumSchema } from "@/data/schemas/private-zoom";
import { PrivateAlbumSchema } from "@/data/schemas/private-zone";
import { PrivateAlbumGallery } from "../private-album-gallery";
@@ -13,11 +13,11 @@ const album = PrivateAlbumSchema.parse({
title: "Private afternoon",
imageCount: 5,
images: [
{ url: "/images/private-zoom/photo-0.png", locked: false, index: 0 },
{ url: "/images/private-zone/photo-0.png", locked: false, index: 0 },
{ url: "", locked: false, index: 1 },
{ url: "/images/private-zoom/locked.png", locked: true, index: 2 },
{ url: "/images/private-zoom/photo-3.png", locked: false, index: 3 },
{ url: "/images/private-zoom/photo-4.png", locked: false, index: 4 },
{ url: "/images/private-zone/locked.png", locked: true, index: 2 },
{ url: "/images/private-zone/photo-3.png", locked: false, index: 3 },
{ url: "/images/private-zone/photo-4.png", locked: false, index: 4 },
],
locked: false,
unlocked: true,
@@ -3,8 +3,8 @@ import Image from "next/image";
import { Copy, ImageIcon, LockKeyhole } from "lucide-react";
import { CharacterAvatar } from "@/app/_components";
import type { PrivateAlbum } from "@/data/schemas/private-zoom";
import { isPrivateAlbumLocked } from "@/lib/private-zoom/private_album";
import type { PrivateAlbum } from "@/data/schemas/private-zone";
import { isPrivateAlbumLocked } from "@/lib/private-zone/private_album";
import {
getPrivateAlbumDisplayImages,
@@ -12,7 +12,7 @@ import {
PRIVATE_ALBUM_GRID_LIMIT,
} from "../private-album-images";
import styles from "../private-zoom-screen.module.css";
import styles from "../private-zone-screen.module.css";
export interface PrivateAlbumCardProps {
album: PrivateAlbum;
@@ -11,14 +11,14 @@ import {
import Image from "next/image";
import { ChevronLeft, ChevronRight, X } from "lucide-react";
import type { PrivateAlbum } from "@/data/schemas/private-zoom";
import type { PrivateAlbum } from "@/data/schemas/private-zone";
import {
getResistedGalleryDragDistance,
resolveGallerySwipeDirection,
} from "../private-album-gallery-motion";
import { getPrivateAlbumDisplayImages } from "../private-album-images";
import styles from "../private-zoom-screen.module.css";
import styles from "../private-zone-screen.module.css";
export interface PrivateAlbumGalleryProps {
album: PrivateAlbum;
@@ -1,4 +1,4 @@
import styles from "../private-zoom-screen.module.css";
import styles from "../private-zone-screen.module.css";
export interface StatusCardProps {
message: string;
@@ -1,6 +1,6 @@
import type { PrivateAlbum } from "@/data/schemas/private-zoom";
import type { PrivateAlbum } from "@/data/schemas/private-zone";
import styles from "../private-zoom-screen.module.css";
import styles from "../private-zone-screen.module.css";
export interface UnlockConfirmDialogProps {
album: PrivateAlbum;
@@ -7,14 +7,14 @@ import {
type RouteSearchParams,
} from "@/router/routes";
export default async function PrivateZoomPage({
export default async function PrivateZonePage({
searchParams,
}: {
searchParams: Promise<RouteSearchParams>;
}) {
redirect(
appendRouteSearchParams(
getCharacterRoutes(DEFAULT_CHARACTER_SLUG).privateZoom,
getCharacterRoutes(DEFAULT_CHARACTER_SLUG).privateZone,
await searchParams,
),
);
@@ -15,7 +15,7 @@ export function getPrivateAlbumGalleryState(input: {
export function buildPrivateAlbumGalleryUrl(
albumId: string,
imageIndex: number,
basePath: string = ROUTES.privateZoom,
basePath: string = ROUTES.privateZone,
): string {
const params = new URLSearchParams({
[PRIVATE_ALBUM_QUERY_PARAM]: albumId,
@@ -24,9 +24,9 @@ export function buildPrivateAlbumGalleryUrl(
return `${basePath}?${params.toString()}`;
}
export function buildPrivateZoomWithoutGalleryUrl(
export function buildPrivateZoneWithoutGalleryUrl(
input: { toString: () => string },
basePath: string = ROUTES.privateZoom,
basePath: string = ROUTES.privateZone,
): string {
const params = new URLSearchParams(input.toString());
params.delete(PRIVATE_ALBUM_QUERY_PARAM);
@@ -1,4 +1,4 @@
import type { PrivateAlbum } from "@/data/schemas/private-zoom";
import type { PrivateAlbum } from "@/data/schemas/private-zone";
export const PRIVATE_ALBUM_GRID_LIMIT = 9;
@@ -17,12 +17,12 @@ import {
useActiveCharacter,
useActiveCharacterRoutes,
} from "@/providers/character-provider";
import { isPrivateAlbumLocked } from "@/lib/private-zoom/private_album";
import { isPrivateAlbumLocked } from "@/lib/private-zone/private_album";
import { useAuthDispatch, useAuthState } from "@/stores/auth/auth-context";
import {
usePrivateZoomDispatch,
usePrivateZoomState,
} from "@/stores/private-zoom";
usePrivateZoneDispatch,
usePrivateZoneState,
} from "@/stores/private-zone";
import {
PrivateAlbumCard,
@@ -30,21 +30,21 @@ import {
StatusCard,
UnlockConfirmDialog,
} from "./components";
import styles from "./private-zoom-screen.module.css";
import styles from "./private-zone-screen.module.css";
import {
isPrivateZoomAuthRequired,
usePrivateZoomBootstrapFlow,
usePrivateZoomUnlockPaywallNavigation,
usePrivateZoomUnlockSuccessRefresh,
} from "./use-private-zoom-flow";
isPrivateZoneAuthRequired,
usePrivateZoneBootstrapFlow,
usePrivateZoneUnlockPaywallNavigation,
usePrivateZoneUnlockSuccessRefresh,
} from "./use-private-zone-flow";
import {
buildPrivateAlbumGalleryUrl,
buildPrivateZoomWithoutGalleryUrl,
buildPrivateZoneWithoutGalleryUrl,
getPrivateAlbumGalleryState,
} from "./private-album-gallery-url";
import { findPrivateAlbumDisplayImage } from "./private-album-images";
export function PrivateZoomScreen() {
export function PrivateZoneScreen() {
const router = useRouter();
const searchParams = useSearchParams();
const navigator = useAppNavigator();
@@ -52,15 +52,15 @@ export function PrivateZoomScreen() {
const characterRoutes = useActiveCharacterRoutes();
const authState = useAuthState();
const authDispatch = useAuthDispatch();
const state = usePrivateZoomState();
const dispatch = usePrivateZoomDispatch();
const state = usePrivateZoneState();
const dispatch = usePrivateZoneDispatch();
const openedGalleryInPageRef = useRef(false);
const galleryState = useMemo(
() => getPrivateAlbumGalleryState(searchParams),
[searchParams],
);
usePrivateZoomBootstrapFlow({
usePrivateZoneBootstrapFlow({
authDispatch,
hasInitialized: authState.hasInitialized,
isAuthLoading: authState.isLoading,
@@ -68,17 +68,17 @@ export function PrivateZoomScreen() {
roomDispatch: dispatch,
roomStatus: state.status,
});
usePrivateZoomUnlockPaywallNavigation({
usePrivateZoneUnlockPaywallNavigation({
loginStatus: authState.loginStatus,
roomDispatch: dispatch,
unlockPaywallRequest: state.unlockPaywallRequest,
});
usePrivateZoomUnlockSuccessRefresh(state.unlockSuccessNonce);
usePrivateZoneUnlockSuccessRefresh(state.unlockSuccessNonce);
const displayName = character.displayName;
const avatarUrl = character.assets.avatar;
const title = character.copy.privateZoomTitle;
const subtitle = character.copy.privateZoomSubtitle;
const title = character.copy.privateZoneTitle;
const subtitle = character.copy.privateZoneSubtitle;
const pendingAlbum = useMemo(
() =>
state.items.find(
@@ -113,15 +113,15 @@ export function PrivateZoomScreen() {
!galleryImage
) {
router.replace(
buildPrivateZoomWithoutGalleryUrl(
buildPrivateZoneWithoutGalleryUrl(
searchParams,
characterRoutes.privateZoom,
characterRoutes.privateZone,
),
{ scroll: false },
);
}
}, [
characterRoutes.privateZoom,
characterRoutes.privateZone,
galleryAlbum,
galleryImage,
galleryState,
@@ -131,15 +131,15 @@ export function PrivateZoomScreen() {
]);
const handleTopUpClick = () => {
if (isPrivateZoomAuthRequired(authState.loginStatus)) {
navigator.openAuth(characterRoutes.privateZoom);
if (isPrivateZoneAuthRequired(authState.loginStatus)) {
navigator.openAuth(characterRoutes.privateZone);
return;
}
navigator.openSubscription({
type: "topup",
returnTo: "private-zoom",
returnTo: "private-zone",
analytics: {
entryPoint: "private_zoom",
entryPoint: "private_zone",
triggerReason: "vip_cta",
},
});
@@ -151,7 +151,7 @@ export function PrivateZoomScreen() {
buildPrivateAlbumGalleryUrl(
albumId,
imageIndex,
characterRoutes.privateZoom,
characterRoutes.privateZone,
),
{ scroll: false },
);
@@ -164,9 +164,9 @@ export function PrivateZoomScreen() {
return;
}
router.replace(
buildPrivateZoomWithoutGalleryUrl(
buildPrivateZoneWithoutGalleryUrl(
searchParams,
characterRoutes.privateZoom,
characterRoutes.privateZone,
),
{ scroll: false },
);
@@ -178,7 +178,7 @@ export function PrivateZoomScreen() {
buildPrivateAlbumGalleryUrl(
galleryAlbum.albumId,
imageIndex,
characterRoutes.privateZoom,
characterRoutes.privateZone,
),
{ scroll: false },
);
@@ -199,7 +199,7 @@ export function PrivateZoomScreen() {
<section className={styles.hero} aria-label={title}>
<div className={styles.heroCover}>
<Image
src={character.assets.privateZoomBanner}
src={character.assets.privateZoneBanner}
alt=""
width={2048}
height={1152}
@@ -225,8 +225,8 @@ export function PrivateZoomScreen() {
<button
type="button"
data-analytics-key="private_zoom.primary_cta"
data-analytics-label="Open private zoom top up"
data-analytics-key="private_zone.primary_cta"
data-analytics-label="Open private zone top up"
className={styles.primaryCta}
onClick={handleTopUpClick}
>
@@ -249,7 +249,7 @@ export function PrivateZoomScreen() {
<StatusCard
message={state.errorMessage}
actionLabel="Retry"
onAction={() => dispatch({ type: "PrivateZoomRefresh" })}
onAction={() => dispatch({ type: "PrivateZoneRefresh" })}
/>
) : null}
@@ -261,7 +261,7 @@ export function PrivateZoomScreen() {
<StatusCard
message="No private albums yet."
actionLabel="Refresh"
onAction={() => dispatch({ type: "PrivateZoomRefresh" })}
onAction={() => dispatch({ type: "PrivateZoneRefresh" })}
/>
) : null}
@@ -279,7 +279,7 @@ export function PrivateZoomScreen() {
}
onUnlock={() =>
dispatch({
type: "PrivateZoomUnlockRequested",
type: "PrivateZoneUnlockRequested",
albumId: album.albumId,
})
}
@@ -289,12 +289,12 @@ export function PrivateZoomScreen() {
</section>
<AppBottomNav
activeItem="privateZoom"
privateZoomLabel={character.copy.privateZoomTitle}
activeItem="privateZone"
privateZoneLabel={character.copy.privateZoneTitle}
onChatClick={() =>
navigator.push(characterRoutes.splash, { scroll: false })
}
onPrivateZoomClick={() => undefined}
onPrivateZoneClick={() => undefined}
onMenuClick={() =>
navigator.push(
buildGlobalPageUrl(ROUTES.profile, characterRoutes.chat),
@@ -308,8 +308,8 @@ export function PrivateZoomScreen() {
album={pendingAlbum}
isUnlocking={state.isUnlocking}
errorMessage={state.unlockErrorMessage}
onCancel={() => dispatch({ type: "PrivateZoomUnlockCancelled" })}
onConfirm={() => dispatch({ type: "PrivateZoomUnlockConfirmed" })}
onCancel={() => dispatch({ type: "PrivateZoneUnlockCancelled" })}
onConfirm={() => dispatch({ type: "PrivateZoneUnlockConfirmed" })}
/>
) : state.unlockErrorMessage ? (
<div className={styles.toast} role="status">
@@ -8,37 +8,37 @@ import { behaviorAnalytics } from "@/lib/analytics";
import { useActiveCharacterRoutes } from "@/providers/character-provider";
import { useAppNavigator } from "@/router/use-app-navigator";
import type { AuthEvent } from "@/stores/auth/auth-events";
import type { PrivateZoomEvent } from "@/stores/private-zoom";
import type { PrivateZoomUnlockPaywallRequest } from "@/stores/private-zoom/private-zoom-state";
import type { PrivateZoneEvent } from "@/stores/private-zone";
import type { PrivateZoneUnlockPaywallRequest } from "@/stores/private-zone/private-zone-state";
import { useUserDispatch } from "@/stores/user/user-context";
export interface UsePrivateZoomBootstrapFlowInput {
export interface UsePrivateZoneBootstrapFlowInput {
authDispatch: Dispatch<AuthEvent>;
hasInitialized: boolean;
isAuthLoading: boolean;
loginStatus: LoginStatus;
roomDispatch: Dispatch<PrivateZoomEvent>;
roomDispatch: Dispatch<PrivateZoneEvent>;
roomStatus: string;
}
export interface UsePrivateZoomUnlockPaywallNavigationInput {
export interface UsePrivateZoneUnlockPaywallNavigationInput {
loginStatus: LoginStatus;
roomDispatch: Dispatch<PrivateZoomEvent>;
unlockPaywallRequest: PrivateZoomUnlockPaywallRequest | null;
roomDispatch: Dispatch<PrivateZoneEvent>;
unlockPaywallRequest: PrivateZoneUnlockPaywallRequest | null;
}
export function isPrivateZoomAuthRequired(loginStatus: LoginStatus): boolean {
export function isPrivateZoneAuthRequired(loginStatus: LoginStatus): boolean {
return loginStatus === "guest" || loginStatus === "notLoggedIn";
}
export function usePrivateZoomBootstrapFlow({
export function usePrivateZoneBootstrapFlow({
authDispatch,
hasInitialized,
isAuthLoading,
loginStatus,
roomDispatch,
roomStatus,
}: UsePrivateZoomBootstrapFlowInput): void {
}: UsePrivateZoneBootstrapFlowInput): void {
const previousLoginStatusRef = useRef(loginStatus);
useGuestLoginBootstrap({
@@ -56,12 +56,12 @@ export function usePrivateZoomBootstrapFlow({
previousLoginStatusRef.current = loginStatus;
if (roomStatus === "idle") {
roomDispatch({ type: "PrivateZoomInit" });
roomDispatch({ type: "PrivateZoneInit" });
return;
}
if (previousLoginStatus !== loginStatus && roomStatus !== "loading") {
roomDispatch({ type: "PrivateZoomRefresh" });
roomDispatch({ type: "PrivateZoneRefresh" });
}
}, [
hasInitialized,
@@ -72,19 +72,19 @@ export function usePrivateZoomBootstrapFlow({
]);
}
export function usePrivateZoomUnlockPaywallNavigation({
export function usePrivateZoneUnlockPaywallNavigation({
loginStatus,
roomDispatch,
unlockPaywallRequest,
}: UsePrivateZoomUnlockPaywallNavigationInput): void {
}: UsePrivateZoneUnlockPaywallNavigationInput): void {
const navigator = useAppNavigator();
const characterRoutes = useActiveCharacterRoutes();
useEffect(() => {
if (!unlockPaywallRequest) return;
if (isPrivateZoomAuthRequired(loginStatus)) {
navigator.openAuth(characterRoutes.privateZoom);
if (isPrivateZoneAuthRequired(loginStatus)) {
navigator.openAuth(characterRoutes.privateZone);
} else {
behaviorAnalytics.paywallShown({
entryPoint: "private_album_unlock",
@@ -92,16 +92,16 @@ export function usePrivateZoomUnlockPaywallNavigation({
});
navigator.openSubscription({
type: "topup",
returnTo: "private-zoom",
returnTo: "private-zone",
analytics: {
entryPoint: "private_album_unlock",
triggerReason: "insufficient_credits",
},
});
}
roomDispatch({ type: "PrivateZoomUnlockPaywallConsumed" });
roomDispatch({ type: "PrivateZoneUnlockPaywallConsumed" });
}, [
characterRoutes.privateZoom,
characterRoutes.privateZone,
loginStatus,
navigator,
roomDispatch,
@@ -109,7 +109,7 @@ export function usePrivateZoomUnlockPaywallNavigation({
]);
}
export function usePrivateZoomUnlockSuccessRefresh(
export function usePrivateZoneUnlockSuccessRefresh(
unlockSuccessNonce: number,
): void {
const userDispatch = useUserDispatch();
+4 -4
View File
@@ -191,14 +191,14 @@ export function ProfileScreen({ returnTo }: ProfileScreenProps) {
<AppBottomNav
activeItem="menu"
privateZoomLabel={
character?.copy.privateZoomTitle ?? "Private Zoom"
privateZoneLabel={
character?.copy.privateZoneTitle ?? "Private Zone"
}
onChatClick={() =>
navigator.push(navigation.splashUrl, { scroll: false })
}
onPrivateZoomClick={() =>
navigator.push(characterRoutes.privateZoom, { scroll: false })
onPrivateZoneClick={() =>
navigator.push(characterRoutes.privateZone, { scroll: false })
}
onMenuClick={() => undefined}
/>
+4 -4
View File
@@ -43,8 +43,8 @@ export function SplashScreen() {
navigator.openChat({ replace: true });
};
const handleOpenPrivateZoom = () => {
navigator.push(characterRoutes.privateZoom, { scroll: false });
const handleOpenPrivateZone = () => {
navigator.push(characterRoutes.privateZone, { scroll: false });
};
const handleOpenSplash = () => {
@@ -96,9 +96,9 @@ export function SplashScreen() {
</div>
<AppBottomNav
activeItem="chat"
privateZoomLabel={character.copy.privateZoomTitle}
privateZoneLabel={character.copy.privateZoneTitle}
onChatClick={handleOpenSplash}
onPrivateZoomClick={handleOpenPrivateZoom}
onPrivateZoneClick={handleOpenPrivateZone}
onMenuClick={handleOpenMenu}
/>
</div>