PageSourceSearch

https://www.hamienet.com/assets/SiteModuleStudio-DC8hHdXc.js

js hamienet.com collected 2026-10-02 05:02:03 UTC 35,670 bytes, 528 lines download raw bytes

1import { r as reactExports, j as jsxRuntimeExports } from "./vendor-react-Bay9Y7Lb.js";
2import { a as api } from "./index-w5lXls15.js";
3import { a as ai, g as getAiModel } from "./ai-B0Mlroaa.js";
4import { q as extractCode } from "./utils-CEVb7nN1.js";
5import { aX as ArrowLeft, eH as WandSparkles, bN as History, b2 as Upload, c4 as RotateCcw, bK as PanelsTopLeft, b0 as FileText, fk as FileCode, bo as Monitor, cf as Plus, fl as FolderSearch, fm as ArrowUpNarrowWide, fn as ArrowDownWideNarrow, cx as List, bn as Globe, e7 as Lock } from "./vendor-D0P4XgNj.js";
6import "./vendor-i18n-BC7ZMXWQ.js";
7import "./vendor-ai-gQv1oOjO.js";
8let DEFAULT_PAGE_CODE = `import React from 'react';
9import { useTranslation } from 'react-i18next';
10
11const NewPage = () => {
12  const { t } = useTranslation();
13  return (
14    <div className="p-6 max-w-[1200px] mx-auto animate-in fade-in">
15      <h1 className="text-3xl font-bold text-zinc-900 dark:text-white mb-4">New Page</h1>
16      <p className="text-zinc-600 dark:text-zinc-400">Start building your content here.</p>
17    </div>
18  );
19};
20
21export default NewPage;`;
22let ICONS = ["layout", "home", "compass", "user", "clock", "thumbs-up", "bell", "tv", "hash", "list-music", "info", "mail", "star", "heart", "settings"];
23const ModuleCard = ({ module, onClick }) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { onClick, className: "group relative bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 rounded-xl p-5 hover:border-indigo-500 hover:shadow-lg transition-all cursor-pointer flex flex-col h-48", children: [
24  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-start mb-3", children: [
25    /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `p-2 rounded-lg ${module.status === "active" ? "bg-indigo-50 text-indigo-600 dark:bg-indigo-900/20 dark:text-indigo-400" : "bg-zinc-100 text-zinc-500 dark:bg-zinc-800"}`, children: /* @__PURE__ */ jsxRuntimeExports.jsx(Globe, { size: 24 }) }),
26    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-end gap-1", children: [
27      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider ${module.status === "active" ? "bg-emerald-100 text-emerald-700" : "bg-amber-100 text-amber-700"}`, children: module.status }),
28      module.visibility === "admin" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-purple-100 text-purple-700 flex items-center gap-1", children: [
29        /* @__PURE__ */ jsxRuntimeExports.jsx(Lock, { size: 8 }),
30        " Admin"
31      ] })
32    ] })
33  ] }),
34  /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "font-bold text-zinc-900 dark:text-white mb-1 group-hover:text-indigo-600 dark:group-hover:text-indigo-400 transition-colors", children: module.name }),
35  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-zinc-500 line-clamp-2 mb-4", children: module.description }),
36  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-auto flex items-center justify-between text-xs text-zinc-400", children: [
37    /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono opacity-70", children: module.path.split("/").pop() }),
38    /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: module.lastUpdated && new Date(module.lastUpdated).toLocaleDateString() })
39  ] })
40] });
41const ModuleListItem = ({ module, onClick }) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { onClick, className: "group flex items-center gap-4 p-4 bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 rounded-xl hover:border-indigo-500 hover:shadow-md transition-all cursor-pointer", children: [
42  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `p-2 rounded-lg shrink-0 ${module.status === "active" ? "bg-indigo-50 text-indigo-600 dark:bg-indigo-900/20 dark:text-indigo-400" : "bg-zinc-100 text-zinc-500 dark:bg-zinc-800"}`, children: /* @__PURE__ */ jsxRuntimeExports.jsx(Globe, { size: 20 }) }),
43  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [
44    /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "font-bold text-zinc-900 dark:text-white text-sm truncate group-hover:text-indigo-600 dark:group-hover:text-indigo-400 transition-colors", children: module.name }),
45    /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-zinc-500 truncate", children: module.description })
46  ] }),
47  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-4 shrink-0", children: [
48    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-end gap-1", children: [
49      /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider ${module.status === "active" ? "bg-emerald-100 text-emerald-700" : "bg-amber-100 text-amber-700"}`, children: module.status }),
50      module.visibility && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] text-zinc-500 uppercase tracking-wider", children: module.visibility })
51    ] }),
52    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-xs text-zinc-400 flex flex-col items-end min-w-[80px]", children: [
53      /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: module.lastUpdated ? new Date(module.lastUpdated).toLocaleDateString() : "N/A" }),
54      /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[10px] opacity-70", children: [
55        "by ",
56        module.updatedBy
57      ] })
58    ] })
59  ] })
60] });
61const SiteModuleStudio = ({ refreshTrigger }) => {
62  const [modules, setModules] = reactExports.useState([]);
63  const [studioConfig, setStudioConfig] = reactExports.useState(null);
64  reactExports.useEffect(() => {
65    fetch("/api/admin/studio/config").then((res) => res.json()).then((data) => {
66      if (data.site) {
67        if (data.site.defaultCode) DEFAULT_PAGE_CODE = data.site.defaultCode;
68        if (data.site.icons) ICONS = data.site.icons;
69        setStudioConfig(data.site);
70      }
71    }).catch(console.error);
72  }, []);
73  const [activeDraft, setActiveDraft] = reactExports.useState(null);
74  const [activeTab, setActiveTab] = reactExports.useState("component");
75  const [isAiGenerating, setIsAiGenerating] = reactExports.useState(false);
76  const [prompt, setPrompt] = reactExports.useState("");
77  const [deployStep, setDeployStep] = reactExports.useState(0);
78  const [history, setHistory] = reactExports.useState([]);
79  const [showHistory, setShowHistory] = reactExports.useState(false);
80  const [viewMode, setViewMode] = reactExports.useState("list");
81  const [sortBy, setSortBy] = reactExports.useState("updated");
82  const [sortDir, setSortDir] = reactExports.useState("desc");
83  reactExports.useEffect(() => {
84    loadModules();
85  }, [refreshTrigger]);
86  reactExports.useEffect(() => {
87    if (activeDraft && !activeDraft.isNew) {
88      loadHistory(activeDraft.id);
89    }
90  }, [activeDraft == null ? void 0 : activeDraft.id]);
91  const loadModules = async () => {
92    const data = await api.getSitePages();
93    setModules(data);
94  };
95  const loadHistory = async (id) => {
96    const data = await api.getSitePageHistory(id);
97    setHistory(data);
98  };
99  const sortedModules = [...modules].sort((a, b) => {
100    let res = 0;
101    if (sortBy === "name") res = a.name.localeCompare(b.name);
102    else if (sortBy === "status") res = a.status.localeCompare(b.status);
103    else res = new Date(a.lastUpdated || 0).getTime() - new Date(b.lastUpdated || 0).getTime();
104    return sortDir === "asc" ? res : -res;
105  });
106  const handleSelect = (mod) => {
107    setActiveDraft({
108      id: mod.id,
109      name: mod.name,
110      description: mod.description,
111      path: mod.path,
112      iconKey: mod.iconKey || "layout",
113      content: mod.content,
114      visibility: mod.visibility || "public",
115      isNew: false
116    });
117    setShowHistory(false);
118    setActiveTab("component");
119  };
120  const handleNew = () => {
121    setActiveDraft({
122      id: `page-${Date.now()}`,
123      name: "Untitled Page",
124      description: "A new public page.",
125      path: "/pages/Site/Untitled.tsx",
126      iconKey: "layout",
127      content: DEFAULT_PAGE_CODE,
128      visibility: "public",
129      isNew: true
130    });
131    setHistory([]);
132    setShowHistory(false);
133    setActiveTab("component");
134  };
135  const handleScanFiles = async () => {
136    var _a;
137    if (!confirm("Scan file system for unregistered Site Pages? This will add any found files in 'pages/Site' to the database.")) return;
138    try {
139      const files = await api.getFileSystem();
140      const siteFiles = files.filter((f) => f.path.startsWith("/pages/Site/") && f.path.endsWith(".tsx"));
141      const dbModules = await api.getSitePages();
142      const existingPaths = new Set(dbModules.map((m) => m.path));
143      let addedCount = 0;
144      for (const file of siteFiles) {
145        const normalizedPath = file.path.startsWith("/") ? file.path : "/" + file.path;
146        const alreadyExists = Array.from(existingPaths).some(
147          (p) => (p.startsWith("/") ? p : "/" + p) === normalizedPath
148        );
149        if (!alreadyExists) {
150          const filename = ((_a = normalizedPath.split("/").pop()) == null ? void 0 : _a.replace(".tsx", "")) || "Unknown";
151          const name = filename.replace(/([A-Z])/g, " $1").trim();
152          const id = filename.toLowerCase().replace(/\s+/g, "_");
153          await api.createSitePage(
154            name,
155            normalizedPath,
156            "Automatically imported from file system.",
157            file.content,
158            "System Scanner",
159            "admin"
160          );
161          addedCount++;
162        }
163      }
164      alert(`Scan complete. Registered ${addedCount} new pages.`);
165      loadModules();
166    } catch (e) {
167      console.error(e);
168      alert("Scan failed.");
169    }
170  };
171  const handleAiGenerate = async () => {
172    if (!prompt) return;
173    setIsAiGenerating(true);
174    try {
175      const currentCode = (activeDraft == null ? void 0 : activeDraft.content) || "";
176      const isUpdate = !(activeDraft == null ? void 0 : activeDraft.isNew) && currentCode.length > 50;
177      const systemPrompt = isUpdate ? `You are an expert React Frontend Developer. UPDATE the following existing page component based on the user request: "${prompt}".
178               Current Code:
179               ${currentCode}
180               
181               Technical Constraints:
182               - Use 'lucide-react' for icons.
183               - Use 'react-i18next'.
184               - Use Tailwind CSS.
185               - Return ONLY the full valid updated code.` : `You are an expert React Frontend Developer.
186                Create a high-quality, production-ready React component for a public website page.
187                
188                User Requirement: "${prompt}"
189                
190                Technical Constraints:
191                - Use functional components with hooks.
192                - Use 'lucide-react' for icons.
193                - Use 'react-i18next' for text.
194                - Use Tailwind CSS for styling (dark mode support).
195                - Return ONLY the raw code string.`;
196      const response = await ai.models.generateContent({
197        model: getAiModel("gemini-3-pro-preview"),
198        // High reasoning
199        contents: systemPrompt
200      });
201      const rawText = response.text || "";
202      const cleanCode = extractCode(rawText);
203      if (activeDraft) {
204        setActiveDraft({
205          ...activeDraft,
206          name: activeDraft.isNew ? prompt.length > 20 ? prompt.substring(0, 20) + "..." : prompt : activeDraft.name,
207          description: activeDraft.isNew ? `AI Generated page based on: "${prompt}"` : activeDraft.description,
208          content: cleanCode
209        });
210      }
211    } catch (e) {
212      console.error("Gemini Generation Error:", e);
213      alert("Failed to generate code. Please check your API key configuration.");
214    } finally {
215      setIsAiGenerating(false);
216      setPrompt("");
217    }
218  };
219  const handleDeploy = async () => {
220    setDeployStep(2);
221    await new Promise((r) => setTimeout(r, 1500));
222    if (activeDraft) {
223      if (activeDraft.isNew) {
224        await api.createSitePage(activeDraft.name, activeDraft.path, activeDraft.description, activeDraft.content, "Admin", activeDraft.visibility);
225      } else {
226        await api.updateSitePage(activeDraft.id, {
227          content: activeDraft.content,
228          name: activeDraft.name,
229          description: activeDraft.description,
230          iconKey: activeDraft.iconKey,
231          visibility: activeDraft.visibility
232        }, "Updated via Studio", "Admin");
233      }
234    }
235    setDeployStep(0);
236    loadModules();
237    if (activeDraft && !activeDraft.isNew) loadHistory(activeDraft.id);
238    alert("Page Deployed Successfully!");
239  };
240  const handleRevert = async (version) => {
241    if (!activeDraft || !confirm(`Revert to version from ${new Date(version.createdAt).toLocaleString()}?`)) return;
242    await api.revertSitePageVersion(activeDraft.id, version.id);
243    const updatedHistory = await api.getSitePageHistory(activeDraft.id);
244    const latestPage = (await api.getSitePages()).find((p) => p.id === activeDraft.id);
245    if (latestPage) {
246      setActiveDraft((prev) => prev ? {
247        ...prev,
248        content: latestPage.content
249      } : null);
250      setHistory(updatedHistory);
251    }
252  };
253  if (activeDraft) {
254    return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col h-full bg-zinc-50 dark:bg-black fixed inset-0 z-[50]", children: [
255      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "h-16 border-b border-zinc-200 dark:border-zinc-800 bg-white dark:bg-zinc-900 px-6 flex items-center justify-between shrink-0", children: [
256        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-4", children: [
257          /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setActiveDraft(null), className: "p-2 hover:bg-zinc-100 dark:hover:bg-zinc-800 rounded-full transition-colors text-zinc-500", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowLeft, { size: 20 }) }),
258          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col", children: [
259            /* @__PURE__ */ jsxRuntimeExports.jsx(
260              "input",
261              {
262                className: "font-bold text-zinc-900 dark:text-white bg-transparent outline-none placeholder:text-zinc-400",
263                value: activeDraft.name,
264                onChange: (e) => setActiveDraft({ ...activeDraft, name: e.target.value }),
265                placeholder: "Page Name"
266              }
267            ),
268            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 text-xs text-zinc-500", children: [
269              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono", children: activeDraft.path }),
270              !activeDraft.isNew && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "bg-zinc-100 dark:bg-zinc-800 px-1.5 rounded", children: [
271                "v",
272                history.length + 1
273              ] })
274            ] })
275          ] })
276        ] }),
277        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3", children: [
278          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "hidden md:flex items-center bg-zinc-100 dark:bg-zinc-800 rounded-lg p-1 border border-zinc-200 dark:border-zinc-700", children: [
279            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-2 text-indigo-500", children: /* @__PURE__ */ jsxRuntimeExports.jsx(WandSparkles, { size: 16, className: isAiGenerating ? "animate-spin" : "" }) }),
280            /* @__PURE__ */ jsxRuntimeExports.jsx(
281              "input",
282              {
283                value: prompt,
284                onChange: (e) => setPrompt(e.target.value),
285                onKeyDown: (e) => e.key === "Enter" && handleAiGenerate(),
286                placeholder: activeDraft.isNew ? "Describe page layout..." : "Describe changes...",
287                className: "bg-transparent border-none outline-none text-xs w-64 text-zinc-900 dark:text-white",
288                disabled: isAiGenerating
289              }
290            ),
291            /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: handleAiGenerate, disabled: !prompt || isAiGenerating, className: "px-3 py-1 bg-white dark:bg-zinc-700 rounded-md text-xs font-bold shadow-sm hover:text-indigo-500 transition-colors", children: activeDraft.isNew ? "Generate" : "Update" })
292          ] }),
293          /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-8 w-px bg-zinc-200 dark:bg-zinc-800 mx-2" }),
294          /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setShowHistory(!showHistory), className: `p-2 rounded-lg hover:bg-zinc-100 dark:hover:bg-zinc-800 text-zinc-500 transition-colors ${showHistory ? "bg-zinc-100 dark:bg-zinc-800 text-indigo-500" : ""}`, title: "Version History", children: /* @__PURE__ */ jsxRuntimeExports.jsx(History, { size: 20 }) }),
295          /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { onClick: () => setDeployStep(1), className: "flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-bold bg-indigo-600 hover:bg-indigo-700 text-white transition-colors shadow-lg shadow-indigo-500/20", children: [
296            /* @__PURE__ */ jsxRuntimeExports.jsx(Upload, { size: 16 }),
297            " Deploy"
298          ] })
299        ] })
300      ] }),
301      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex overflow-hidden", children: [
302        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-72 bg-white dark:bg-zinc-900 border-r border-zinc-200 dark:border-zinc-800 flex flex-col shrink-0 transition-all", children: showHistory ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col h-full", children: [
303          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 border-b border-zinc-200 dark:border-zinc-800 font-bold text-xs uppercase text-zinc-500 flex justify-between items-center", children: [
304            "Version History",
305            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "bg-zinc-100 dark:bg-zinc-800 px-1.5 py-0.5 rounded", children: history.length })
306          ] }),
307          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 overflow-y-auto p-2 space-y-2", children: [
308            history.length === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-center text-xs text-zinc-500 py-4", children: "No history available." }),
309            history.map((ver) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-3 rounded-lg border border-zinc-200 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-950 hover:border-indigo-300 dark:hover:border-indigo-700 transition-colors group", children: [
310              /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-start mb-1", children: [
311                /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-bold text-xs text-zinc-900 dark:text-white", children: [
312                  "v",
313                  ver.versionNumber
314                ] }),
315                /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] text-zinc-500", children: new Date(ver.createdAt).toLocaleDateString() })
316              ] }),
317              /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-zinc-500 line-clamp-2 mb-2", children: ver.changeLog }),
318              /* @__PURE__ */ jsxRuntimeExports.jsxs(
319                "button",
320                {
321                  onClick: () => handleRevert(ver),
322                  className: "w-full py-1 text-[10px] font-bold bg-white dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded text-zinc-600 dark:text-zinc-400 hover:text-indigo-500 hover:border-indigo-500 transition-colors flex items-center justify-center gap-1",
323                  children: [
324                    /* @__PURE__ */ jsxRuntimeExports.jsx(RotateCcw, { size: 10 }),
325                    " Revert to this version"
326                  ]
327                }
328              )
329            ] }, ver.id))
330          ] })
331        ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
332          /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-4 border-b border-zinc-200 dark:border-zinc-800 font-bold text-xs uppercase text-zinc-500", children: "Configuration" }),
333          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 space-y-6 overflow-y-auto", children: [
334            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
335              /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold text-zinc-700 dark:text-zinc-300 mb-2", children: "Icon" }),
336              /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "grid grid-cols-5 gap-2", children: ICONS.map((icon) => /* @__PURE__ */ jsxRuntimeExports.jsx(
337                "button",
338                {
339                  onClick: () => setActiveDraft({ ...activeDraft, iconKey: icon }),
340                  className: `aspect-square rounded-lg flex items-center justify-center border transition-all ${activeDraft.iconKey === icon ? "bg-indigo-50 border-indigo-500 text-indigo-600" : "bg-zinc-50 dark:bg-zinc-800 border-transparent text-zinc-400 hover:border-zinc-300"}`,
341                  children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-4 h-4 bg-current rounded-full" })
342                },
343                icon
344              )) })
345            ] }),
346            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-3", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
347              /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold text-zinc-700 dark:text-zinc-300 mb-2", children: "Visibility" }),
348              /* @__PURE__ */ jsxRuntimeExports.jsxs(
349                "select",
350                {
351                  value: activeDraft.visibility,
352                  onChange: (e) => setActiveDraft({ ...activeDraft, visibility: e.target.value }),
353                  className: "w-full bg-zinc-50 dark:bg-zinc-950 border border-zinc-200 dark:border-zinc-800 rounded-lg p-2 text-xs outline-none focus:border-indigo-500 text-zinc-900 dark:text-white",
354                  children: [
355                    /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "public", children: "Public (Everyone)" }),
356                    /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "private", children: "Private (Hidden)" }),
357                    /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "admin", children: "Admin Only" })
358                  ]
359                }
360              )
361            ] }) })
362          ] })
363        ] }) }),
364        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex flex-col bg-[#1e1e1e]", children: [
365          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center bg-[#252526] border-b border-[#333]", children: [
366            /* @__PURE__ */ jsxRuntimeExports.jsxs(
367              "button",
368              {
369                onClick: () => setActiveTab("component"),
370                className: `px-6 py-3 text-xs font-bold flex items-center gap-2 border-r border-[#333] transition-colors ${activeTab === "component" ? "bg-[#1e1e1e] text-white border-t-2 border-t-indigo-500" : "text-zinc-500 hover:text-zinc-300"}`,
371                children: [
372                  /* @__PURE__ */ jsxRuntimeExports.jsx(PanelsTopLeft, { size: 14 }),
373                  " Page Component (.tsx)"
374                ]
375              }
376            ),
377            /* @__PURE__ */ jsxRuntimeExports.jsxs(
378              "button",
379              {
380                onClick: () => setActiveTab("description"),
381                className: `px-6 py-3 text-xs font-bold flex items-center gap-2 border-r border-[#333] transition-colors ${activeTab === "description" ? "bg-[#1e1e1e] text-white border-t-2 border-t-indigo-500" : "text-zinc-500 hover:text-zinc-300"}`,
382                children: [
383                  /* @__PURE__ */ jsxRuntimeExports.jsx(FileText, { size: 14 }),
384                  " Documentation (DB)"
385                ]
386              }
387            )
388          ] }),
389          /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 relative", children: activeTab === "component" ? /* @__PURE__ */ jsxRuntimeExports.jsx(
390            "textarea",
391            {
392              value: activeDraft.content,
393              onChange: (e) => setActiveDraft({
394                ...activeDraft,
395                content: e.target.value
396              }),
397              className: "absolute inset-0 w-full h-full bg-transparent text-zinc-300 font-mono text-sm p-6 resize-none outline-none",
398              spellCheck: false
399            }
400          ) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "absolute inset-0 w-full h-full bg-[#1e1e1e] p-6", children: [
401            /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold text-zinc-500 uppercase mb-2", children: "Module Description (Stored in Database)" }),
402            /* @__PURE__ */ jsxRuntimeExports.jsx(
403              "textarea",
404              {
405                value: activeDraft.description,
406                onChange: (e) => setActiveDraft({ ...activeDraft, description: e.target.value }),
407                className: "w-full h-full bg-[#252526] text-zinc-300 p-4 resize-none outline-none border border-[#333] rounded-lg focus:border-indigo-500 transition-colors font-sans text-sm leading-relaxed",
408                placeholder: "Describe the functionality and purpose of this page..."
409              }
410            )
411          ] }) })
412        ] })
413      ] }),
414      deployStep > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "fixed inset-0 z-[100] flex items-center justify-center p-4", children: [
415        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0 bg-black/80 backdrop-blur-sm", onClick: () => setDeployStep(0) }),
416        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "relative w-full max-w-2xl bg-white dark:bg-zinc-900 rounded-xl shadow-2xl p-8 border border-zinc-200 dark:border-zinc-800 animate-in zoom-in-95", children: deployStep === 1 ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
417          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-4 mb-6", children: [
418            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-3 bg-indigo-100 dark:bg-indigo-900/30 rounded-full text-indigo-600 dark:text-indigo-400", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Upload, { size: 24 }) }),
419            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
420              /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "text-xl font-bold text-zinc-900 dark:text-white", children: "Deploy Site Page?" }),
421              /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-zinc-500 dark:text-zinc-400", children: activeDraft.isNew ? "This will create a new page visible in the Site Navigation." : "This will update the live page and archive the current version." })
422            ] })
423          ] }),
424          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-3 mb-8", children: [
425            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between p-3 bg-zinc-50 dark:bg-zinc-800 rounded-lg border border-zinc-200 dark:border-zinc-700", children: [
426              /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3", children: [
427                /* @__PURE__ */ jsxRuntimeExports.jsx(FileCode, { size: 18, className: "text-blue-500" }),
428                /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm font-bold text-zinc-700 dark:text-zinc-300", children: activeDraft.path })
429              ] }),
430              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `text-xs px-2 py-0.5 rounded font-bold ${activeDraft.isNew ? "bg-emerald-100 text-emerald-700" : "bg-amber-100 text-amber-700"}`, children: activeDraft.isNew ? "CREATE" : "UPDATE" })
431            ] }),
432            activeDraft.isNew && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-3 bg-blue-50 dark:bg-blue-900/20 rounded-lg border border-blue-100 dark:border-blue-900/30 text-xs text-blue-700 dark:text-blue-300 flex items-start gap-2", children: [
433              /* @__PURE__ */ jsxRuntimeExports.jsx(Monitor, { size: 14, className: "mt-0.5" }),
434              "The page will be automatically registered in the App router and Side Navigation."
435            ] })
436          ] }),
437          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-3 justify-end", children: [
438            /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setDeployStep(0), className: "px-4 py-2 text-zinc-500 hover:text-zinc-900 dark:hover:text-white font-bold", children: "Cancel" }),
439            /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: handleDeploy, className: "px-6 py-2 bg-indigo-600 hover:bg-indigo-700 text-white rounded-lg font-bold shadow-lg shadow-indigo-500/20", children: "Confirm Deployment" })
440          ] })
441        ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-center py-8", children: [
442          /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-16 h-16 border-4 border-indigo-500 border-t-transparent rounded-full animate-spin mx-auto mb-4" }),
443          /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-xl font-bold text-zinc-900 dark:text-white", children: "Deploying..." }),
444          /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-zinc-500 mt-2", children: "Compiling assets and updating routing table." })
445        ] }) })
446      ] })
447    ] });
448  }
449  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-6 max-w-[1600px] mx-auto h-full flex flex-col animate-in fade-in", children: [
450    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center mb-8", children: [
451      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
452        /* @__PURE__ */ jsxRuntimeExports.jsxs("h1", { className: "text-2xl font-bold text-white flex items-center gap-2", children: [
453          /* @__PURE__ */ jsxRuntimeExports.jsx(Monitor, { size: 28, className: "text-indigo-500" }),
454          " Site Architect"
455        ] }),
456        /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-zinc-400 mt-1", children: "Manage public facing pages and landing content." })
457      ] }),
458      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [
459        /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { onClick: handleNew, className: "bg-white text-black hover:opacity-90 px-4 py-2 rounded-lg font-bold flex items-center gap-2 transition-all", children: [
460          /* @__PURE__ */ jsxRuntimeExports.jsx(Plus, { size: 18 }),
461          " New Page"
462        ] }),
463        /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { onClick: handleScanFiles, className: "bg-indigo-900/30 text-indigo-300 hover:bg-indigo-900/50 px-4 py-2 rounded-lg font-bold flex items-center gap-2 transition-all border border-indigo-800", children: [
464          /* @__PURE__ */ jsxRuntimeExports.jsx(FolderSearch, { size: 18 }),
465          " Scan Files"
466        ] })
467      ] })
468    ] }),
469    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row justify-between items-end sm:items-center mb-6 gap-4", children: [
470      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center bg-zinc-900 border border-zinc-800 rounded-lg p-1", children: [
471        /* @__PURE__ */ jsxRuntimeExports.jsxs(
472          "select",
473          {
474            value: sortBy,
475            onChange: (e) => setSortBy(e.target.value),
476            className: "bg-transparent text-xs font-bold text-zinc-300 outline-none px-2 py-1 cursor-pointer",
477            children: [
478              /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "updated", children: "Last Updated" }),
479              /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "name", children: "Name" }),
480              /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "status", children: "Status" })
481            ]
482          }
483        ),
484        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-px h-4 bg-zinc-800 mx-1" }),
485        /* @__PURE__ */ jsxRuntimeExports.jsx(
486          "button",
487          {
488            onClick: () => setSortDir((prev) => prev === "asc" ? "desc" : "asc"),
489            className: "p-1 hover:bg-zinc-800 rounded text-zinc-500",
490            children: sortDir === "asc" ? /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowUpNarrowWide, { size: 14 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowDownWideNarrow, { size: 14 })
491          }
492        )
493      ] }) }),
494      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex bg-zinc-800 p-1 rounded-lg border border-zinc-700", children: [
495        /* @__PURE__ */ jsxRuntimeExports.jsx(
496          "button",
497          {
498            onClick: () => setViewMode("grid"),
499            className: `p-1.5 rounded-md transition-all ${viewMode === "grid" ? "bg-zinc-700 shadow text-indigo-400" : "text-zinc-500 hover:text-zinc-300"}`,
500            children: /* @__PURE__ */ jsxRuntimeExports.jsx(PanelsTopLeft, { size: 16 })
501          }
502        ),
503        /* @__PURE__ */ jsxRuntimeExports.jsx(
504          "button",
505          {
506            onClick: () => setViewMode("list"),
507            className: `p-1.5 rounded-md transition-all ${viewMode === "list" ? "bg-zinc-700 shadow text-indigo-400" : "text-zinc-500 hover:text-zinc-300"}`,
508            children: /* @__PURE__ */ jsxRuntimeExports.jsx(List, { size: 16 })
509          }
510        )
511      ] })
512    ] }),
513    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: viewMode === "grid" ? "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6 overflow-y-auto custom-scrollbar flex-1 min-h-0 pb-20" : "flex flex-col gap-3 overflow-y-auto custom-scrollbar flex-1 min-h-0 pb-20", children: [
514      sortedModules.map((mod) => viewMode === "grid" ? /* @__PURE__ */ jsxRuntimeExports.jsx(ModuleCard, { module: mod, onClick: () => {
515        handleSelect(mod);
516      } }, mod.id) : /* @__PURE__ */ jsxRuntimeExports.jsx(ModuleListItem, { module: mod, onClick: () => {
517        handleSelect(mod);
518      } }, mod.id)),
519      viewMode === "grid" && /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { onClick: handleNew, className: "border-2 border-dashed border-zinc-300 dark:border-zinc-800 rounded-xl p-5 flex flex-col items-center justify-center text-zinc-400 hover:border-indigo-500 hover:text-indigo-500 transition-all h-48 group bg-zinc-50/50 dark:bg-zinc-900/50", children: [
520        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-12 h-12 rounded-full bg-zinc-100 dark:bg-zinc-800 group-hover:bg-indigo-50 dark:group-hover:bg-indigo-900/20 flex items-center justify-center mb-2 transition-colors", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Plus, { size: 24 }) }),
521        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-bold text-sm", children: "Create New" })
522      ] })
523    ] })
524  ] });
525};
526export {
527  SiteModuleStudio as default
528};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.