Kaynağa Gözat

fix(studio): restore book settings route

Ma 6 ay önce
ebeveyn
işleme
7b2dc110cd

+ 1 - 0
packages/studio/src/App.test.ts

@@ -7,6 +7,7 @@ describe("deriveActiveBookId", () => {
     expect(deriveActiveBookId({ page: "chapter", bookId: "beta", chapterNumber: 3 })).toBe("beta");
     expect(deriveActiveBookId({ page: "truth", bookId: "gamma" })).toBe("gamma");
     expect(deriveActiveBookId({ page: "analytics", bookId: "delta" })).toBe("delta");
+    expect(deriveActiveBookId({ page: "book-settings", bookId: "epsilon" })).toBe("epsilon");
   });
 
   it("returns undefined for non-book routes", () => {

+ 7 - 0
packages/studio/src/App.tsx

@@ -5,6 +5,7 @@ import { Sidebar } from "./components/Sidebar";
 import { Dashboard } from "./pages/Dashboard";
 import { ChatPage } from "./pages/ChatPage";
 import { BookCreate } from "./pages/BookCreate";
+import { BookDetail } from "./pages/BookDetail";
 import { ChapterReader } from "./pages/ChapterReader";
 import { Analytics } from "./pages/Analytics";
 import { ServiceListPage } from "./pages/ServiceListPage";
@@ -67,6 +68,7 @@ export function App() {
   const nav = {
     toDashboard: () => setRoute({ page: "dashboard" }),
     toBook: (bookId: string) => setRoute({ page: "book", bookId }),
+    toBookSettings: (bookId: string) => setRoute({ page: "book-settings", bookId }),
     toBookCreate: () => setRoute({ page: "book-create" }),
     toChapter: (bookId: string, chapterNumber: number) =>
       setRoute({ page: "chapter", bookId, chapterNumber }),
@@ -188,6 +190,11 @@ export function App() {
               <BookSidebarToggle bookId={route.bookId} theme={theme} t={t} sse={sse} />
             </div>
           )}
+          {route.page === "book-settings" && (
+            <div className="max-w-4xl mx-auto px-6 py-12 md:px-12 lg:py-16 fade-in">
+              <BookDetail bookId={route.bookId} nav={nav} theme={theme} t={t} sse={sse} />
+            </div>
+          )}
           {route.page === "chapter" && (
             <div className="max-w-4xl mx-auto px-6 py-12 md:px-12 lg:py-16 fade-in">
               <ChapterReader bookId={route.bookId} chapterNumber={route.chapterNumber} nav={nav} theme={theme} t={t} />

+ 8 - 0
packages/studio/src/hooks/use-hash-route.test.ts

@@ -15,6 +15,10 @@ describe("hash route", () => {
       expect(parseHash("#/book/my-novel")).toEqual({ page: "book", bookId: "my-novel" });
     });
 
+    it("parses book settings route", () => {
+      expect(parseHash("#/book/my-novel/settings")).toEqual({ page: "book-settings", bookId: "my-novel" });
+    });
+
     it("decodes encoded bookId", () => {
       expect(parseHash("#/book/%E4%B9%9D%E9%BE%99")).toEqual({ page: "book", bookId: "九龙" });
     });
@@ -53,6 +57,10 @@ describe("hash route", () => {
       expect(routeToHash({ page: "book", bookId: "novel-1" })).toBe("#/book/novel-1");
     });
 
+    it("book-settings -> #/book/{id}/settings", () => {
+      expect(routeToHash({ page: "book-settings", bookId: "novel-1" })).toBe("#/book/novel-1/settings");
+    });
+
     it("encodes Chinese bookId", () => {
       const hash = routeToHash({ page: "book", bookId: "九龙城夜行" });
       expect(hash).toContain("#/book/");

+ 6 - 1
packages/studio/src/hooks/use-hash-route.ts

@@ -3,6 +3,7 @@ import { useState, useEffect, useCallback } from "react";
 export type HashRoute =
   | { page: "dashboard" }
   | { page: "book"; bookId: string }
+  | { page: "book-settings"; bookId: string }
   | { page: "book-create" }
   | { page: "services" }
   | { page: "service-detail"; serviceId: string }
@@ -27,6 +28,9 @@ function parseHash(hash: string): HashRoute {
   const serviceMatch = path.match(/^services\/([^/]+)$/);
   if (serviceMatch) return { page: "service-detail", serviceId: decodeURIComponent(serviceMatch[1]) };
 
+  const bookSettingsMatch = path.match(/^book\/([^/]+)\/settings$/);
+  if (bookSettingsMatch) return { page: "book-settings", bookId: decodeURIComponent(bookSettingsMatch[1]) };
+
   const bookMatch = path.match(/^book\/([^/]+)$/);
   if (bookMatch) return { page: "book", bookId: decodeURIComponent(bookMatch[1]) };
 
@@ -37,6 +41,7 @@ function routeToHash(route: HashRoute): string {
   switch (route.page) {
     case "dashboard": return "#/";
     case "book": return `#/book/${encodeURIComponent(route.bookId)}`;
+    case "book-settings": return `#/book/${encodeURIComponent(route.bookId)}/settings`;
     case "book-create": return "#/book/new";
     case "services": return "#/services";
     case "service-detail": return `#/services/${encodeURIComponent(route.serviceId)}`;
@@ -46,7 +51,7 @@ function routeToHash(route: HashRoute): string {
 
 export { parseHash, routeToHash }; // for testing
 
-const HASH_PAGES = new Set(["dashboard", "book", "book-create", "services", "service-detail"]);
+const HASH_PAGES = new Set(["dashboard", "book", "book-settings", "book-create", "services", "service-detail"]);
 
 export function useHashRoute() {
   const [route, setRouteState] = useState<HashRoute>(() => parseHash(window.location.hash));

+ 2 - 1
packages/studio/src/pages/Dashboard.tsx

@@ -36,6 +36,7 @@ interface BookSummary {
 
 interface Nav {
   toBook: (id: string) => void;
+  toBookSettings: (id: string) => void;
   toAnalytics: (id: string) => void;
   toBookCreate: () => void;
   toServices: () => void;
@@ -87,7 +88,7 @@ function BookMenu({ bookId, bookTitle, nav, t, onDelete, onOpenChange }: {
       {open && (
         <div className="absolute right-0 top-full mt-1 w-44 bg-card border border-border rounded-xl shadow-lg shadow-primary/5 py-1 z-50 fade-in">
           <button
-            onClick={() => { setOpen(false); nav.toBook(bookId); }}
+            onClick={() => { setOpen(false); nav.toBookSettings(bookId); }}
             className="w-full flex items-center gap-2.5 px-4 py-2.5 text-sm text-foreground hover:bg-secondary/50 transition-colors cursor-pointer"
           >
             <Settings size={14} className="text-muted-foreground" />