refactor(routes): move shared pages to global scope

This commit is contained in:
2026-07-20 14:02:15 +08:00
parent b216b53f2e
commit 1f7ab2be04
36 changed files with 743 additions and 251 deletions
@@ -2,8 +2,6 @@ import { act } from "react";
import { createRoot, type Root } from "react-dom/client";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { getCharacterBySlug } from "@/data/constants/character";
import { CharacterProvider } from "@/providers/character-provider";
import { Result } from "@/utils/result";
import { FeedbackScreen } from "../feedback-screen";
@@ -13,8 +11,8 @@ const mocks = vi.hoisted(() => ({
submitFeedback: vi.fn(),
}));
vi.mock("@/router/use-app-navigator", () => ({
useAppNavigator: () => ({ replace: mocks.replace }),
vi.mock("@/router/use-global-app-navigator", () => ({
useGlobalAppNavigator: () => ({ replace: mocks.replace }),
}));
vi.mock("@/lib/feedback", () => ({
@@ -69,6 +67,11 @@ describe("FeedbackScreen", () => {
.querySelector("textarea")
?.getAttribute("aria-describedby"),
).toBe("feedback-content-hint");
expect(
container.querySelector<HTMLAnchorElement>(
'[data-analytics-key="feedback.back_to_sidebar"]',
)?.getAttribute("href"),
).toBe("/sidebar?returnTo=%2Fcharacters%2Fmaya%2Fchat");
});
it("submits valid text and renders the feedback id", async () => {
@@ -103,18 +106,13 @@ describe("FeedbackScreen", () => {
});
expect(container.textContent).toContain("feedback-123");
expect(container.textContent).toContain("Thank you for helping us.");
expect(container.textContent).toContain("Back to Sidebar");
});
});
function renderFeedbackScreen(root: Root): void {
const character = getCharacterBySlug("maya");
if (!character) throw new Error("Missing Maya character fixture");
act(() =>
root.render(
<CharacterProvider character={character}>
<FeedbackScreen />
</CharacterProvider>,
),
root.render(<FeedbackScreen returnTo="/characters/maya/chat" />),
);
}
+18 -11
View File
@@ -17,8 +17,11 @@ import {
import { BackButton } from "@/app/_components";
import { MobileShell } from "@/app/_components/core";
import type { FeedbackCategory } from "@/data/schemas/feedback";
import { useActiveCharacterRoutes } from "@/providers/character-provider";
import { useAppNavigator } from "@/router/use-app-navigator";
import {
resolveGlobalRouteContext,
type GlobalReturnToValue,
} from "@/router/global-route-context";
import { useGlobalAppNavigator } from "@/router/use-global-app-navigator";
import {
FEEDBACK_IMAGE_ACCEPT,
@@ -45,9 +48,13 @@ const CATEGORY_OPTIONS: readonly CategoryOption[] = [
{ value: "other", label: "Other", icon: MessageCircleMore },
];
export function FeedbackScreen() {
const navigator = useAppNavigator();
const characterRoutes = useActiveCharacterRoutes();
export interface FeedbackScreenProps {
returnTo?: GlobalReturnToValue;
}
export function FeedbackScreen({ returnTo }: FeedbackScreenProps) {
const navigator = useGlobalAppNavigator();
const navigation = resolveGlobalRouteContext(returnTo);
const form = useFeedbackSubmission();
if (form.feedbackId) {
@@ -70,11 +77,11 @@ export function FeedbackScreen() {
</div>
<button
type="button"
data-analytics-key="feedback.back_to_chat"
data-analytics-key="feedback.back_to_sidebar"
className={styles.primaryButton}
onClick={() => navigator.replace(characterRoutes.chat)}
onClick={() => navigator.replace(navigation.sidebarUrl)}
>
Back to Chat
Back to Sidebar
</button>
</section>
</main>
@@ -95,10 +102,10 @@ export function FeedbackScreen() {
<header className={styles.header}>
<BackButton
href={characterRoutes.chat}
href={navigation.sidebarUrl}
variant="soft"
ariaLabel="Back to chat"
analyticsKey="feedback.back_to_chat"
ariaLabel="Back to sidebar"
analyticsKey="feedback.back_to_sidebar"
/>
<div className={styles.headingBlock}>
<h1 className={styles.title}>Help us improve CozSweet</h1>
+4 -13
View File
@@ -1,21 +1,12 @@
import { redirect } from "next/navigation";
import type { RouteSearchParams } from "@/router/routes";
import { DEFAULT_CHARACTER_SLUG } from "@/data/constants/character";
import {
appendRouteSearchParams,
getCharacterRoutes,
type RouteSearchParams,
} from "@/router/routes";
import { FeedbackScreen } from "./feedback-screen";
export default async function FeedbackPage({
searchParams,
}: {
searchParams: Promise<RouteSearchParams>;
}) {
redirect(
appendRouteSearchParams(
getCharacterRoutes(DEFAULT_CHARACTER_SLUG).feedback,
await searchParams,
),
);
const query = await searchParams;
return <FeedbackScreen returnTo={query.returnTo} />;
}