@morphika/webframe 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (319) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +46 -0
  3. package/admin/assets.ts +4 -0
  4. package/admin/database.ts +4 -0
  5. package/admin/index.ts +6 -0
  6. package/admin/login.ts +4 -0
  7. package/admin/navigation.ts +4 -0
  8. package/admin/pages-editor.ts +4 -0
  9. package/admin/pages.ts +4 -0
  10. package/admin/projects-editor.ts +4 -0
  11. package/admin/projects.ts +4 -0
  12. package/admin/settings.ts +4 -0
  13. package/admin/setup.ts +4 -0
  14. package/admin/storage.ts +4 -0
  15. package/admin/styles.ts +4 -0
  16. package/app/(site)/[slug]/loading.tsx +20 -0
  17. package/app/(site)/[slug]/page.tsx +83 -0
  18. package/app/(site)/error.tsx +32 -0
  19. package/app/(site)/layout.tsx +53 -0
  20. package/app/(site)/loading.tsx +20 -0
  21. package/app/(site)/not-found.tsx +41 -0
  22. package/app/(site)/page.tsx +43 -0
  23. package/app/(site)/preview/page.tsx +99 -0
  24. package/app/(site)/work/[slug]/loading.tsx +23 -0
  25. package/app/(site)/work/[slug]/page.tsx +84 -0
  26. package/app/admin/assets/page.tsx +573 -0
  27. package/app/admin/database/page.tsx +302 -0
  28. package/app/admin/error.tsx +53 -0
  29. package/app/admin/layout.tsx +273 -0
  30. package/app/admin/login/page.tsx +88 -0
  31. package/app/admin/navigation/page.tsx +157 -0
  32. package/app/admin/page.tsx +17 -0
  33. package/app/admin/pages/[slug]/page.tsx +849 -0
  34. package/app/admin/pages/page.tsx +588 -0
  35. package/app/admin/projects/[slug]/page.tsx +3 -0
  36. package/app/admin/projects/page.tsx +669 -0
  37. package/app/admin/settings/page.tsx +132 -0
  38. package/app/admin/setup/page.tsx +64 -0
  39. package/app/admin/storage/page.tsx +518 -0
  40. package/app/admin/styles/page.tsx +243 -0
  41. package/app/api/admin/assets/file/route.ts +81 -0
  42. package/app/api/admin/assets/health/route.ts +170 -0
  43. package/app/api/admin/assets/register/route.ts +163 -0
  44. package/app/api/admin/assets/registry/route.ts +98 -0
  45. package/app/api/admin/assets/relink/confirm/route.ts +242 -0
  46. package/app/api/admin/assets/relink/route.ts +202 -0
  47. package/app/api/admin/assets/scan/route.ts +271 -0
  48. package/app/api/admin/auth/route.ts +160 -0
  49. package/app/api/admin/custom-sections/[slug]/route.ts +159 -0
  50. package/app/api/admin/custom-sections/route.ts +127 -0
  51. package/app/api/admin/database/route.ts +53 -0
  52. package/app/api/admin/pages/[slug]/duplicate/route.ts +91 -0
  53. package/app/api/admin/pages/[slug]/route.ts +617 -0
  54. package/app/api/admin/pages/[slug]/set-home/route.ts +76 -0
  55. package/app/api/admin/pages/route.ts +129 -0
  56. package/app/api/admin/preview/route.ts +53 -0
  57. package/app/api/admin/r2/connect/route.ts +181 -0
  58. package/app/api/admin/r2/delete/route.ts +198 -0
  59. package/app/api/admin/r2/disconnect/route.ts +42 -0
  60. package/app/api/admin/r2/rename/route.ts +265 -0
  61. package/app/api/admin/r2/status/route.ts +106 -0
  62. package/app/api/admin/r2/upload-url/route.ts +148 -0
  63. package/app/api/admin/revalidate/route.ts +55 -0
  64. package/app/api/admin/settings/route.ts +279 -0
  65. package/app/api/admin/setup/complete/route.ts +51 -0
  66. package/app/api/admin/setup/route.ts +118 -0
  67. package/app/api/admin/storage/switch/route.ts +117 -0
  68. package/app/api/admin/styles/fonts/route.ts +97 -0
  69. package/app/api/admin/styles/route.ts +304 -0
  70. package/app/api/assets/[...path]/route.ts +98 -0
  71. package/app/api/custom-sections/[id]/route.ts +43 -0
  72. package/app/api/draft-mode/disable/route.ts +10 -0
  73. package/app/api/draft-mode/enable/route.ts +26 -0
  74. package/app/api/projects/route.ts +42 -0
  75. package/app/api/styles/route.ts +88 -0
  76. package/app/favicon.ico +0 -0
  77. package/app/globals.css +7 -0
  78. package/app/layout.tsx +53 -0
  79. package/app/robots.ts +17 -0
  80. package/app/sitemap.ts +48 -0
  81. package/app/studio/[[...index]]/page.tsx +8 -0
  82. package/components/admin/MetadataEditor.tsx +173 -0
  83. package/components/admin/PublishToggle.tsx +130 -0
  84. package/components/admin/icons.tsx +40 -0
  85. package/components/admin/nav-builder/NavBuilder.tsx +182 -0
  86. package/components/admin/nav-builder/NavBuilderGrid.tsx +326 -0
  87. package/components/admin/nav-builder/NavGeneralSettings.tsx +275 -0
  88. package/components/admin/nav-builder/NavGridCell.tsx +48 -0
  89. package/components/admin/nav-builder/NavGridItem.tsx +189 -0
  90. package/components/admin/nav-builder/NavItemSettings.tsx +288 -0
  91. package/components/admin/nav-builder/NavItemTypePicker.tsx +102 -0
  92. package/components/admin/nav-builder/NavLivePreview.tsx +125 -0
  93. package/components/admin/nav-builder/NavSettingsFields.tsx +248 -0
  94. package/components/admin/nav-builder/NavSettingsPanel.tsx +127 -0
  95. package/components/admin/nav-builder/index.ts +10 -0
  96. package/components/admin/nav-builder/nav-builder-utils.ts +238 -0
  97. package/components/admin/setup-wizard/BrandingStep.tsx +218 -0
  98. package/components/admin/setup-wizard/DatabaseStep.tsx +331 -0
  99. package/components/admin/setup-wizard/DoneStep.tsx +187 -0
  100. package/components/admin/setup-wizard/SetupWizard.tsx +166 -0
  101. package/components/admin/setup-wizard/StorageStep.tsx +308 -0
  102. package/components/admin/setup-wizard/WelcomeStep.tsx +96 -0
  103. package/components/admin/setup-wizard/index.ts +9 -0
  104. package/components/admin/styles/ColorsEditor.tsx +214 -0
  105. package/components/admin/styles/FontsEditor.tsx +258 -0
  106. package/components/admin/styles/GridLayoutEditor.tsx +292 -0
  107. package/components/admin/styles/LinksButtonsEditor.tsx +120 -0
  108. package/components/admin/styles/TypographyEditor.tsx +266 -0
  109. package/components/admin/styles/index.ts +9 -0
  110. package/components/admin/styles/shared.tsx +68 -0
  111. package/components/blocks/BlockRenderer.tsx +404 -0
  112. package/components/blocks/ButtonBlockRenderer.tsx +52 -0
  113. package/components/blocks/CoverBlockRenderer.tsx +239 -0
  114. package/components/blocks/CustomSectionInstanceRenderer.tsx +82 -0
  115. package/components/blocks/EnterAnimationWrapper.tsx +140 -0
  116. package/components/blocks/HoverAnimationWrapper.tsx +308 -0
  117. package/components/blocks/ImageBlockRenderer.tsx +61 -0
  118. package/components/blocks/ImageGridBlockRenderer.tsx +545 -0
  119. package/components/blocks/PageBackground.tsx +28 -0
  120. package/components/blocks/PageNavAnimation.tsx +35 -0
  121. package/components/blocks/PageNavColor.tsx +24 -0
  122. package/components/blocks/PageRenderer.tsx +142 -0
  123. package/components/blocks/ParallaxGroupRenderer.tsx +448 -0
  124. package/components/blocks/ParallaxSlideRenderer.tsx +175 -0
  125. package/components/blocks/ProjectGridBlockRenderer.tsx +556 -0
  126. package/components/blocks/SectionRenderer.tsx +170 -0
  127. package/components/blocks/SectionV2Renderer.tsx +330 -0
  128. package/components/blocks/ShaderCanvas.tsx +392 -0
  129. package/components/blocks/SpacerBlockRenderer.tsx +17 -0
  130. package/components/blocks/TextBlockRenderer.tsx +87 -0
  131. package/components/blocks/TypewriterRichText.tsx +464 -0
  132. package/components/blocks/TypewriterWrapper.tsx +149 -0
  133. package/components/blocks/VideoBlockRenderer.tsx +304 -0
  134. package/components/blocks/index.ts +2 -0
  135. package/components/builder/AssetBrowser.tsx +2 -0
  136. package/components/builder/BlockLivePreview.tsx +101 -0
  137. package/components/builder/BlockTypePicker.tsx +178 -0
  138. package/components/builder/BuilderCanvas.tsx +354 -0
  139. package/components/builder/CanvasMinimap.tsx +200 -0
  140. package/components/builder/CanvasToolbar.tsx +202 -0
  141. package/components/builder/ColorPicker.tsx +243 -0
  142. package/components/builder/ColorSwatchPicker.tsx +274 -0
  143. package/components/builder/ColumnDragContext.tsx +51 -0
  144. package/components/builder/ColumnDragOverlay.tsx +110 -0
  145. package/components/builder/CustomSectionInstanceCard.tsx +97 -0
  146. package/components/builder/DeviceFrame.tsx +123 -0
  147. package/components/builder/DndWrapper.tsx +337 -0
  148. package/components/builder/InsertionLines.tsx +186 -0
  149. package/components/builder/ParallaxGroupCanvas.tsx +228 -0
  150. package/components/builder/ParallaxSlideHeader.tsx +113 -0
  151. package/components/builder/ReadOnlyFrame.tsx +417 -0
  152. package/components/builder/SectionEditorBar.tsx +288 -0
  153. package/components/builder/SectionTypePicker.tsx +422 -0
  154. package/components/builder/SectionV2Canvas.tsx +297 -0
  155. package/components/builder/SectionV2Column.tsx +488 -0
  156. package/components/builder/SettingsPanel.tsx +911 -0
  157. package/components/builder/SortableBlock.tsx +230 -0
  158. package/components/builder/SortableRow.tsx +362 -0
  159. package/components/builder/VirtualAssetGrid.tsx +397 -0
  160. package/components/builder/asset-browser/AssetBrowser.tsx +178 -0
  161. package/components/builder/asset-browser/FileLightbox.tsx +116 -0
  162. package/components/builder/asset-browser/FolderTreeItem.tsx +55 -0
  163. package/components/builder/asset-browser/R2BrowserContent.tsx +436 -0
  164. package/components/builder/asset-browser/R2ContextMenu.tsx +98 -0
  165. package/components/builder/asset-browser/VideoThumbnail.tsx +63 -0
  166. package/components/builder/asset-browser/helpers.ts +88 -0
  167. package/components/builder/asset-browser/index.ts +1 -0
  168. package/components/builder/asset-browser/types.ts +49 -0
  169. package/components/builder/asset-browser/useAssetBrowser.ts +344 -0
  170. package/components/builder/asset-browser/useR2DragDrop.ts +116 -0
  171. package/components/builder/asset-browser/useR2Operations.ts +189 -0
  172. package/components/builder/blockStyles.tsx +295 -0
  173. package/components/builder/editors/ButtonBlockEditor.tsx +184 -0
  174. package/components/builder/editors/CoverBlockEditor.tsx +488 -0
  175. package/components/builder/editors/EnterAnimationPicker.tsx +297 -0
  176. package/components/builder/editors/HoverEffectPicker.tsx +209 -0
  177. package/components/builder/editors/ImageBlockEditor.tsx +206 -0
  178. package/components/builder/editors/ImageGridBlockEditor.tsx +386 -0
  179. package/components/builder/editors/ProjectGridEditor.tsx +648 -0
  180. package/components/builder/editors/SpacerBlockEditor.tsx +167 -0
  181. package/components/builder/editors/StaggerSettings.tsx +108 -0
  182. package/components/builder/editors/TextAlignmentIcons.tsx +39 -0
  183. package/components/builder/editors/TextBlockEditor.tsx +462 -0
  184. package/components/builder/editors/TextStylePicker.tsx +183 -0
  185. package/components/builder/editors/VideoBlockEditor.tsx +278 -0
  186. package/components/builder/editors/index.ts +10 -0
  187. package/components/builder/editors/shared.tsx +345 -0
  188. package/components/builder/hooks/useColumnDrag.ts +472 -0
  189. package/components/builder/hooks/useColumnResize.ts +221 -0
  190. package/components/builder/index.ts +12 -0
  191. package/components/builder/live-preview/LiveButtonPreview.tsx +38 -0
  192. package/components/builder/live-preview/LiveCoverPreview.tsx +146 -0
  193. package/components/builder/live-preview/LiveImageGridPreview.tsx +123 -0
  194. package/components/builder/live-preview/LiveImagePreview.tsx +107 -0
  195. package/components/builder/live-preview/LiveProjectGridPreview.tsx +1010 -0
  196. package/components/builder/live-preview/LiveSpacerPreview.tsx +9 -0
  197. package/components/builder/live-preview/LiveTextEditor.tsx +198 -0
  198. package/components/builder/live-preview/LiveVideoPreview.tsx +98 -0
  199. package/components/builder/live-preview/index.ts +10 -0
  200. package/components/builder/live-preview/shared.tsx +153 -0
  201. package/components/builder/settings-panel/BlockLayoutTab.tsx +532 -0
  202. package/components/builder/settings-panel/BlockSettings.tsx +94 -0
  203. package/components/builder/settings-panel/ColumnV2Settings.tsx +160 -0
  204. package/components/builder/settings-panel/LayoutTab.tsx +310 -0
  205. package/components/builder/settings-panel/PageSettings.tsx +200 -0
  206. package/components/builder/settings-panel/ParallaxGroupSettings.tsx +118 -0
  207. package/components/builder/settings-panel/ParallaxSlideSettings.tsx +178 -0
  208. package/components/builder/settings-panel/SectionV2AnimationTab.tsx +103 -0
  209. package/components/builder/settings-panel/SectionV2LayoutTab.tsx +312 -0
  210. package/components/builder/settings-panel/SectionV2Settings.tsx +323 -0
  211. package/components/builder/settings-panel/TRBLInputs.tsx +51 -0
  212. package/components/builder/settings-panel/index.ts +19 -0
  213. package/components/builder/settings-panel/responsive-helpers.ts +524 -0
  214. package/components/ui/CustomCursor.tsx +118 -0
  215. package/components/ui/NavContentLightbox.tsx +152 -0
  216. package/components/ui/Navbar.tsx +582 -0
  217. package/components/ui/PortfolioTracker.tsx +87 -0
  218. package/components/ui/ScrollToTop.tsx +47 -0
  219. package/lib/animation/enter-presets.ts +147 -0
  220. package/lib/animation/enter-resolve.ts +90 -0
  221. package/lib/animation/enter-types.ts +128 -0
  222. package/lib/animation/hover-effect-presets.ts +210 -0
  223. package/lib/animation/hover-effect-types.ts +126 -0
  224. package/lib/asset-retry.ts +111 -0
  225. package/lib/assets.ts +92 -0
  226. package/lib/audit.ts +35 -0
  227. package/lib/auth-token.ts +94 -0
  228. package/lib/auth.ts +13 -0
  229. package/lib/builder/cascade-helpers.ts +51 -0
  230. package/lib/builder/cascade.ts +533 -0
  231. package/lib/builder/constants.ts +103 -0
  232. package/lib/builder/defaults.ts +182 -0
  233. package/lib/builder/history.ts +48 -0
  234. package/lib/builder/index.ts +21 -0
  235. package/lib/builder/layout-styles.ts +344 -0
  236. package/lib/builder/masonry.ts +166 -0
  237. package/lib/builder/responsive.ts +156 -0
  238. package/lib/builder/serializer.ts +845 -0
  239. package/lib/builder/store-blocks.ts +193 -0
  240. package/lib/builder/store-canvas.ts +319 -0
  241. package/lib/builder/store-helpers.ts +490 -0
  242. package/lib/builder/store-sections.ts +709 -0
  243. package/lib/builder/store.ts +333 -0
  244. package/lib/builder/templates.ts +297 -0
  245. package/lib/builder/types.ts +374 -0
  246. package/lib/builder/utils.ts +37 -0
  247. package/lib/color-utils.ts +116 -0
  248. package/lib/config/index.ts +57 -0
  249. package/lib/config/types.ts +122 -0
  250. package/lib/contexts/AssetContext.tsx +79 -0
  251. package/lib/contexts/NavAnimationContext.tsx +44 -0
  252. package/lib/contexts/NavColorContext.tsx +38 -0
  253. package/lib/contexts/PageExitContext.tsx +194 -0
  254. package/lib/contexts/ThumbStatusContext.tsx +83 -0
  255. package/lib/csrf-client.ts +34 -0
  256. package/lib/csrf.ts +68 -0
  257. package/lib/format-utils.ts +24 -0
  258. package/lib/hooks/useViewport.ts +42 -0
  259. package/lib/logger.ts +81 -0
  260. package/lib/revalidate.ts +23 -0
  261. package/lib/sanitize.ts +91 -0
  262. package/lib/sanity/client.ts +8 -0
  263. package/lib/sanity/queries.ts +486 -0
  264. package/lib/sanity/types.ts +869 -0
  265. package/lib/sanity/writeClient.ts +24 -0
  266. package/lib/security.ts +402 -0
  267. package/lib/setup/detect.ts +156 -0
  268. package/lib/shader/glsl/index.ts +27 -0
  269. package/lib/shader/glsl/pixelate.ts +51 -0
  270. package/lib/shader/glsl/rgb-shift.ts +45 -0
  271. package/lib/shader/glsl/ripple.ts +46 -0
  272. package/lib/shader/glsl/vertex.ts +14 -0
  273. package/lib/storage/index.ts +211 -0
  274. package/lib/storage/r2-adapter.ts +286 -0
  275. package/lib/storage/types.ts +125 -0
  276. package/lib/styles/provider.tsx +267 -0
  277. package/lib/thumbnails/generate.ts +151 -0
  278. package/lib/utils.ts +6 -0
  279. package/package.json +212 -0
  280. package/sanity/compose.ts +65 -0
  281. package/sanity/sanity.config.ts +126 -0
  282. package/sanity/schemas/assetRegistry.ts +301 -0
  283. package/sanity/schemas/blocks/blockLayout.ts +90 -0
  284. package/sanity/schemas/blocks/buttonBlock.ts +82 -0
  285. package/sanity/schemas/blocks/coverBlock.ts +229 -0
  286. package/sanity/schemas/blocks/imageBlock.ts +58 -0
  287. package/sanity/schemas/blocks/imageGridBlock.ts +112 -0
  288. package/sanity/schemas/blocks/index.ts +9 -0
  289. package/sanity/schemas/blocks/projectGridBlock.ts +251 -0
  290. package/sanity/schemas/blocks/spacerBlock.ts +41 -0
  291. package/sanity/schemas/blocks/textBlock.ts +139 -0
  292. package/sanity/schemas/blocks/videoBlock.ts +80 -0
  293. package/sanity/schemas/customSection.ts +69 -0
  294. package/sanity/schemas/customSectionInstance.ts +163 -0
  295. package/sanity/schemas/index.ts +111 -0
  296. package/sanity/schemas/objects/enterAnimationConfig.ts +72 -0
  297. package/sanity/schemas/objects/hoverEffectConfig.ts +90 -0
  298. package/sanity/schemas/objects/parallaxGroup.ts +66 -0
  299. package/sanity/schemas/objects/parallaxSlide.ts +217 -0
  300. package/sanity/schemas/objects/typewriterConfig.ts +38 -0
  301. package/sanity/schemas/page.ts +162 -0
  302. package/sanity/schemas/pageSection.ts +157 -0
  303. package/sanity/schemas/pageSectionV2.ts +269 -0
  304. package/sanity/schemas/siteSettings.ts +256 -0
  305. package/sanity/schemas/siteStyles.ts +210 -0
  306. package/site/error.ts +4 -0
  307. package/site/index.ts +8 -0
  308. package/site/not-found.ts +4 -0
  309. package/site/page.ts +4 -0
  310. package/site/preview.ts +4 -0
  311. package/site/robots.ts +4 -0
  312. package/site/sitemap.ts +4 -0
  313. package/site/work.ts +4 -0
  314. package/studio/index.ts +4 -0
  315. package/styles/admin.css +85 -0
  316. package/styles/animations.css +237 -0
  317. package/styles/base.css +148 -0
  318. package/styles/globals.css +10 -0
  319. package/tsconfig.json +25 -0
@@ -0,0 +1,302 @@
1
+ "use client";
2
+
3
+ import { useState, useEffect, useCallback } from "react";
4
+
5
+ // ── Types ──
6
+
7
+ interface SanityStats {
8
+ totalDocuments: number;
9
+ pages: number;
10
+ publishedPages: number;
11
+ draftPages: number;
12
+ projects: number;
13
+ siteSettings: number;
14
+ siteStyles: number;
15
+ assetRegistry: number;
16
+ datasets: string[];
17
+ }
18
+
19
+ interface SanityStatus {
20
+ connected: boolean;
21
+ projectId: string;
22
+ dataset: string;
23
+ hasWriteToken: boolean;
24
+ stats: SanityStats | null;
25
+ error?: string;
26
+ apiVersion: string;
27
+ }
28
+
29
+ // ── Stat Card ──
30
+
31
+ function StatCard({ label, value, sub }: { label: string; value: number | string; sub?: string }) {
32
+ return (
33
+ <div className="bg-white rounded-xl border border-neutral-200 p-4">
34
+ <p className="text-[11px] uppercase tracking-wider text-neutral-400 mb-1">{label}</p>
35
+ <p className="text-2xl font-semibold text-neutral-900">{value}</p>
36
+ {sub && <p className="text-xs text-neutral-400 mt-0.5">{sub}</p>}
37
+ </div>
38
+ );
39
+ }
40
+
41
+ // ── Config Field (read-only display) ──
42
+
43
+ function ConfigField({ label, value, masked }: { label: string; value: string; masked?: boolean }) {
44
+ const [revealed, setRevealed] = useState(false);
45
+ const display = masked && !revealed ? "••••••••••••" : value || "—";
46
+
47
+ return (
48
+ <div className="flex items-center justify-between py-3 border-b border-neutral-100 last:border-0">
49
+ <span className="text-sm text-neutral-500">{label}</span>
50
+ <div className="flex items-center gap-2">
51
+ <code className="text-sm text-neutral-800 bg-neutral-50 px-2 py-0.5 rounded font-mono">{display}</code>
52
+ {masked && value && (
53
+ <button
54
+ onClick={() => setRevealed(!revealed)}
55
+ className="text-xs text-[#076bff] hover:underline"
56
+ >
57
+ {revealed ? "Hide" : "Show"}
58
+ </button>
59
+ )}
60
+ </div>
61
+ </div>
62
+ );
63
+ }
64
+
65
+ // ── Document Type Row ──
66
+
67
+ function DocTypeRow({ label, count, icon }: { label: string; count: number; icon: React.ReactNode }) {
68
+ return (
69
+ <div className="flex items-center justify-between py-2.5 border-b border-neutral-100 last:border-0">
70
+ <div className="flex items-center gap-2.5">
71
+ <span className="text-neutral-400">{icon}</span>
72
+ <span className="text-sm text-neutral-700">{label}</span>
73
+ </div>
74
+ <span className="text-sm font-medium text-neutral-900 bg-neutral-100 rounded-full px-2.5 py-0.5 min-w-[32px] text-center">
75
+ {count}
76
+ </span>
77
+ </div>
78
+ );
79
+ }
80
+
81
+ // ── Main Page ──
82
+
83
+ export default function AdminDatabasePage() {
84
+ const [status, setStatus] = useState<SanityStatus | null>(null);
85
+ const [loading, setLoading] = useState(true);
86
+ const [testing, setTesting] = useState(false);
87
+
88
+ const fetchStatus = useCallback(async () => {
89
+ try {
90
+ const res = await fetch("/api/admin/database");
91
+ if (res.ok) {
92
+ const data = await res.json();
93
+ setStatus(data);
94
+ }
95
+ } catch {
96
+ setStatus(null);
97
+ } finally {
98
+ setLoading(false);
99
+ }
100
+ }, []);
101
+
102
+ useEffect(() => {
103
+ fetchStatus();
104
+ }, [fetchStatus]);
105
+
106
+ const handleTestConnection = async () => {
107
+ setTesting(true);
108
+ setLoading(false);
109
+ try {
110
+ const res = await fetch("/api/admin/database");
111
+ if (res.ok) {
112
+ const data = await res.json();
113
+ setStatus(data);
114
+ }
115
+ } catch {
116
+ // handled by status state
117
+ } finally {
118
+ setTesting(false);
119
+ }
120
+ };
121
+
122
+ // ── Loading ──
123
+ if (loading) {
124
+ return (
125
+ <div className="flex items-center justify-center py-20">
126
+ <span className="text-sm text-neutral-400 animate-pulse">Connecting to database...</span>
127
+ </div>
128
+ );
129
+ }
130
+
131
+ const stats = status?.stats;
132
+ const isConnected = status?.connected === true;
133
+
134
+ return (
135
+ <div className="space-y-8 max-w-4xl">
136
+ {/* Header */}
137
+ <div className="flex items-center justify-between">
138
+ <div>
139
+ <h1 className="text-2xl font-semibold text-neutral-900">Database</h1>
140
+ <p className="text-sm text-neutral-400 mt-1">Sanity CMS connection and data overview</p>
141
+ </div>
142
+ <button
143
+ onClick={handleTestConnection}
144
+ disabled={testing}
145
+ className="flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-lg border border-neutral-200 bg-white text-neutral-700 hover:bg-neutral-50 transition-colors disabled:opacity-50"
146
+ >
147
+ {testing ? (
148
+ <svg className="w-4 h-4 animate-spin" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
149
+ <path d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83" />
150
+ </svg>
151
+ ) : (
152
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
153
+ <polyline points="23 4 23 10 17 10" />
154
+ <path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10" />
155
+ </svg>
156
+ )}
157
+ {testing ? "Testing..." : "Test Connection"}
158
+ </button>
159
+ </div>
160
+
161
+ {/* Connection status banner */}
162
+ <div className={`flex items-center gap-3 p-4 rounded-xl border ${
163
+ isConnected
164
+ ? "border-green-200 bg-green-50"
165
+ : "border-red-200 bg-red-50"
166
+ }`}>
167
+ <div className={`w-2.5 h-2.5 rounded-full ${isConnected ? "bg-green-500" : "bg-red-500"}`} />
168
+ <div className="flex-1">
169
+ <p className={`text-sm font-medium ${isConnected ? "text-green-800" : "text-red-800"}`}>
170
+ {isConnected ? "Connected to Sanity" : "Connection Failed"}
171
+ </p>
172
+ {status?.error && (
173
+ <p className="text-xs text-red-600 mt-0.5">{status.error}</p>
174
+ )}
175
+ </div>
176
+ {isConnected && (
177
+ <span className="text-xs text-green-600 bg-green-100 px-2 py-0.5 rounded-full">
178
+ API v{status?.apiVersion}
179
+ </span>
180
+ )}
181
+ </div>
182
+
183
+ {/* Stats grid */}
184
+ {stats && (
185
+ <div className="grid grid-cols-2 sm:grid-cols-4 gap-4">
186
+ <StatCard label="Total Documents" value={stats.totalDocuments} />
187
+ <StatCard label="Pages" value={stats.pages} sub={`${stats.publishedPages} published, ${stats.draftPages} draft`} />
188
+ <StatCard label="Projects" value={stats.projects} />
189
+ <StatCard label="Dataset" value={status?.dataset || "—"} />
190
+ </div>
191
+ )}
192
+
193
+ {/* Two-column layout: Config + Schema */}
194
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
195
+
196
+ {/* Connection Configuration */}
197
+ <div className="bg-white rounded-xl border border-neutral-200 overflow-hidden">
198
+ <div className="px-5 py-4 border-b border-neutral-100">
199
+ <h2 className="text-sm font-semibold text-neutral-900">Connection Configuration</h2>
200
+ <p className="text-xs text-neutral-400 mt-0.5">Values from environment variables — edit in .env.local or hosting dashboard</p>
201
+ </div>
202
+ <div className="px-5 py-2">
203
+ <ConfigField label="Project ID" value={status?.projectId || ""} />
204
+ <ConfigField label="Dataset" value={status?.dataset || ""} />
205
+ <ConfigField label="API Version" value={status?.apiVersion || ""} />
206
+ <ConfigField label="Write Token" value={status?.hasWriteToken ? "Configured" : "Not set"} />
207
+ <ConfigField label="CDN" value="Disabled (real-time reads)" />
208
+ </div>
209
+ </div>
210
+
211
+ {/* Document Types */}
212
+ {stats && (
213
+ <div className="bg-white rounded-xl border border-neutral-200 overflow-hidden">
214
+ <div className="px-5 py-4 border-b border-neutral-100">
215
+ <h2 className="text-sm font-semibold text-neutral-900">Schema Overview</h2>
216
+ <p className="text-xs text-neutral-400 mt-0.5">Document types and counts in this dataset</p>
217
+ </div>
218
+ <div className="px-5 py-2">
219
+ <DocTypeRow
220
+ label="Pages"
221
+ count={stats.pages}
222
+ icon={
223
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8Z" /><polyline points="14 2 14 8 20 8" /></svg>
224
+ }
225
+ />
226
+ <DocTypeRow
227
+ label="Projects"
228
+ count={stats.projects}
229
+ icon={
230
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5"><rect x="2" y="2" width="20" height="20" rx="2.18" /><line x1="7" y1="2" x2="7" y2="22" /><line x1="17" y1="2" x2="17" y2="22" /><line x1="2" y1="12" x2="22" y2="12" /></svg>
231
+ }
232
+ />
233
+ <DocTypeRow
234
+ label="Site Settings"
235
+ count={stats.siteSettings}
236
+ icon={
237
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5"><circle cx="12" cy="12" r="3" /><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9c.38.17.62.55.68.95" /></svg>
238
+ }
239
+ />
240
+ <DocTypeRow
241
+ label="Site Styles"
242
+ count={stats.siteStyles}
243
+ icon={
244
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5"><circle cx="13.5" cy="6.5" r="0.5" fill="currentColor" /><circle cx="17.5" cy="10.5" r="0.5" fill="currentColor" /><circle cx="8.5" cy="7.5" r="0.5" fill="currentColor" /><circle cx="6.5" cy="12" r="0.5" fill="currentColor" /><path d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10c.926 0 1.648-.746 1.648-1.688 0-.437-.18-.835-.437-1.125-.29-.289-.438-.652-.438-1.125a1.64 1.64 0 0 1 1.668-1.668h1.996c3.051 0 5.555-2.503 5.555-5.554C21.965 6.012 17.461 2 12 2Z" /></svg>
245
+ }
246
+ />
247
+ <DocTypeRow
248
+ label="Asset Registry"
249
+ count={stats.assetRegistry}
250
+ icon={
251
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5"><ellipse cx="12" cy="5" rx="9" ry="3" /><path d="M21 12c0 1.66-4.03 3-9 3s-9-1.34-9-3" /><path d="M3 5v14c0 1.66 4.03 3 9 3s9-1.34 9-3V5" /></svg>
252
+ }
253
+ />
254
+ </div>
255
+ </div>
256
+ )}
257
+ </div>
258
+
259
+ {/* Sanity Studio link */}
260
+ <div className="bg-white rounded-xl border border-neutral-200 p-5">
261
+ <div className="flex items-center justify-between">
262
+ <div>
263
+ <h2 className="text-sm font-semibold text-neutral-900">Sanity Studio</h2>
264
+ <p className="text-xs text-neutral-400 mt-0.5">Access the raw Sanity Studio for advanced data editing</p>
265
+ </div>
266
+ <a
267
+ href="/studio"
268
+ target="_blank"
269
+ rel="noopener noreferrer"
270
+ className="flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-lg bg-neutral-900 text-white hover:bg-neutral-800 transition-colors"
271
+ >
272
+ Open Studio
273
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
274
+ <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" />
275
+ <polyline points="15 3 21 3 21 9" />
276
+ <line x1="10" y1="14" x2="21" y2="3" />
277
+ </svg>
278
+ </a>
279
+ </div>
280
+ </div>
281
+
282
+ {/* Env guide */}
283
+ <div className="bg-neutral-50 rounded-xl border border-neutral-200 p-5">
284
+ <h2 className="text-sm font-semibold text-neutral-700 mb-3">Environment Variables Reference</h2>
285
+ <div className="space-y-2 font-mono text-xs text-neutral-500">
286
+ <div className="flex gap-3">
287
+ <span className="text-neutral-400 w-64 shrink-0">NEXT_PUBLIC_SANITY_PROJECT_ID</span>
288
+ <span className="text-neutral-600">Sanity project ID (required)</span>
289
+ </div>
290
+ <div className="flex gap-3">
291
+ <span className="text-neutral-400 w-64 shrink-0">NEXT_PUBLIC_SANITY_DATASET</span>
292
+ <span className="text-neutral-600">Dataset name (default: &quot;production&quot;)</span>
293
+ </div>
294
+ <div className="flex gap-3">
295
+ <span className="text-neutral-400 w-64 shrink-0">SANITY_API_TOKEN</span>
296
+ <span className="text-neutral-600">Write token for mutations (server-only)</span>
297
+ </div>
298
+ </div>
299
+ </div>
300
+ </div>
301
+ );
302
+ }
@@ -0,0 +1,53 @@
1
+ "use client";
2
+
3
+ import { useEffect } from "react";
4
+
5
+ export default function AdminError({
6
+ error,
7
+ reset,
8
+ }: {
9
+ error: Error & { digest?: string };
10
+ reset: () => void;
11
+ }) {
12
+ useEffect(() => {
13
+ console.error("[AdminError]", error);
14
+ }, [error]);
15
+
16
+ return (
17
+ <div className="min-h-screen flex flex-col items-center justify-center bg-white px-6 text-center">
18
+ <div className="w-12 h-12 rounded-full bg-red-50 flex items-center justify-center mb-4">
19
+ <svg
20
+ width="24"
21
+ height="24"
22
+ viewBox="0 0 24 24"
23
+ fill="none"
24
+ stroke="#dc2626"
25
+ strokeWidth="2"
26
+ aria-hidden="true"
27
+ >
28
+ <circle cx="12" cy="12" r="10" />
29
+ <line x1="12" y1="8" x2="12" y2="12" />
30
+ <line x1="12" y1="16" x2="12.01" y2="16" />
31
+ </svg>
32
+ </div>
33
+ <h1 className="text-xl font-semibold text-gray-900">
34
+ Admin Error
35
+ </h1>
36
+ <p className="mt-2 text-sm text-gray-500 max-w-sm">
37
+ Something went wrong in the admin panel.
38
+ {error.digest && (
39
+ <span className="block mt-1 text-xs text-gray-400">
40
+ Error ID: {error.digest}
41
+ </span>
42
+ )}
43
+ </p>
44
+ <button
45
+ onClick={reset}
46
+ className="mt-6 px-5 py-2 rounded-lg text-sm font-medium text-white transition-colors"
47
+ style={{ backgroundColor: "#076bff" }}
48
+ >
49
+ Try again
50
+ </button>
51
+ </div>
52
+ );
53
+ }
@@ -0,0 +1,273 @@
1
+ "use client";
2
+
3
+ import { usePathname, useRouter } from "next/navigation";
4
+ import Link from "next/link";
5
+ import { useState, useEffect, useRef } from "react";
6
+ import { getSiteConfig } from "../../lib/config";
7
+
8
+ // ============================================
9
+ // Navigation Configuration — flat list, no sections
10
+ // ============================================
11
+
12
+ const navLinks = [
13
+ { href: "/admin/pages", label: "Pages", icon: "file" },
14
+ { href: "/admin/projects", label: "Projects", icon: "film" },
15
+ { href: "/admin/styles", label: "Customize", icon: "palette" },
16
+ { href: "/admin/navigation", label: "Navigation", icon: "nav" },
17
+ { href: "/admin/storage", label: "Storage", icon: "harddisk" },
18
+ { href: "/admin/database", label: "Database", icon: "database" },
19
+ { href: "/admin/settings", label: "Metadata", icon: "code" },
20
+ ];
21
+
22
+ // ============================================
23
+ // Icon Component — white stroke for dark sidebar
24
+ // ============================================
25
+
26
+ function NavIcon({ icon, active }: { icon: string; active?: boolean }) {
27
+ const size = 18;
28
+ const color = active ? "currentColor" : "currentColor";
29
+ void active; // active state handled by parent text color
30
+ void color;
31
+ switch (icon) {
32
+ case "file":
33
+ return (
34
+ <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
35
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8Z" />
36
+ <polyline points="14 2 14 8 20 8" />
37
+ </svg>
38
+ );
39
+ case "film":
40
+ return (
41
+ <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
42
+ <rect x="2" y="2" width="20" height="20" rx="2.18" ry="2.18" />
43
+ <line x1="7" y1="2" x2="7" y2="22" />
44
+ <line x1="17" y1="2" x2="17" y2="22" />
45
+ <line x1="2" y1="12" x2="22" y2="12" />
46
+ <line x1="2" y1="7" x2="7" y2="7" />
47
+ <line x1="2" y1="17" x2="7" y2="17" />
48
+ <line x1="17" y1="7" x2="22" y2="7" />
49
+ <line x1="17" y1="17" x2="22" y2="17" />
50
+ </svg>
51
+ );
52
+ case "palette":
53
+ return (
54
+ <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
55
+ <circle cx="13.5" cy="6.5" r="0.5" fill="currentColor" />
56
+ <circle cx="17.5" cy="10.5" r="0.5" fill="currentColor" />
57
+ <circle cx="8.5" cy="7.5" r="0.5" fill="currentColor" />
58
+ <circle cx="6.5" cy="12" r="0.5" fill="currentColor" />
59
+ <path d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10c.926 0 1.648-.746 1.648-1.688 0-.437-.18-.835-.437-1.125-.29-.289-.438-.652-.438-1.125a1.64 1.64 0 0 1 1.668-1.668h1.996c3.051 0 5.555-2.503 5.555-5.554C21.965 6.012 17.461 2 12 2Z" />
60
+ </svg>
61
+ );
62
+ case "nav":
63
+ return (
64
+ <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
65
+ <line x1="3" y1="6" x2="21" y2="6" />
66
+ <line x1="3" y1="12" x2="15" y2="12" />
67
+ <line x1="3" y1="18" x2="18" y2="18" />
68
+ </svg>
69
+ );
70
+ case "database":
71
+ return (
72
+ <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
73
+ <ellipse cx="12" cy="5" rx="9" ry="3" />
74
+ <path d="M21 12c0 1.66-4.03 3-9 3s-9-1.34-9-3" />
75
+ <path d="M3 5v14c0 1.66 4.03 3 9 3s9-1.34 9-3V5" />
76
+ </svg>
77
+ );
78
+ case "harddisk":
79
+ return (
80
+ <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
81
+ <path d="M22 12H2" />
82
+ <path d="M5.45 5.11L2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11Z" />
83
+ <line x1="6" y1="16" x2="6.01" y2="16" strokeWidth="2" />
84
+ <line x1="10" y1="16" x2="10.01" y2="16" strokeWidth="2" />
85
+ </svg>
86
+ );
87
+ case "code":
88
+ return (
89
+ <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
90
+ <polyline points="16 18 22 12 16 6" />
91
+ <polyline points="8 6 2 12 8 18" />
92
+ </svg>
93
+ );
94
+ default:
95
+ return null;
96
+ }
97
+ }
98
+
99
+ // ============================================
100
+ // Layout Component
101
+ // ============================================
102
+
103
+ export default function AdminLayout({
104
+ children,
105
+ }: {
106
+ children: React.ReactNode;
107
+ }) {
108
+ const pathname = usePathname();
109
+ const router = useRouter();
110
+
111
+ // Detect if we're inside the page builder (editing a specific page or project)
112
+ const isPageBuilder =
113
+ /^\/admin\/pages\/[^/]+$/.test(pathname) ||
114
+ /^\/admin\/projects\/[^/]+$/.test(pathname);
115
+
116
+ // Start collapsed if loading directly into the builder
117
+ const [sidebarOpen, setSidebarOpen] = useState(!isPageBuilder);
118
+ const prevPathname = useRef(pathname);
119
+
120
+ // Auto-collapse sidebar when entering the page builder,
121
+ // auto-expand when leaving it
122
+ useEffect(() => {
123
+ if (pathname === prevPathname.current) return;
124
+ const wasInBuilder =
125
+ /^\/admin\/pages\/[^/]+$/.test(prevPathname.current) ||
126
+ /^\/admin\/projects\/[^/]+$/.test(prevPathname.current);
127
+ prevPathname.current = pathname;
128
+
129
+ if (isPageBuilder && !wasInBuilder) {
130
+ setSidebarOpen(false);
131
+ } else if (!isPageBuilder && wasInBuilder) {
132
+ setSidebarOpen(true);
133
+ }
134
+ }, [pathname, isPageBuilder]);
135
+
136
+ // Don't show admin shell on login or setup pages
137
+ if (pathname === "/admin/login" || pathname === "/admin/setup") {
138
+ return <>{children}</>;
139
+ }
140
+
141
+ const handleLogout = async () => {
142
+ await fetch("/api/admin/auth", { method: "DELETE" });
143
+ router.push("/admin/login");
144
+ router.refresh();
145
+ };
146
+
147
+ // Check if a link is active
148
+ const isLinkActive = (href: string) => {
149
+ return pathname === href || pathname.startsWith(href + "/");
150
+ };
151
+
152
+ return (
153
+ <div data-admin className="flex h-screen bg-[#f8f8f8]" style={{ fontFamily: "Inter, system-ui, sans-serif" }}>
154
+ {/* Sidebar — Dark */}
155
+ <aside
156
+ className={`flex flex-col bg-[#141414] transition-all duration-200 ${
157
+ sidebarOpen ? "w-56" : "w-16"
158
+ }`}
159
+ >
160
+ {/* Logo */}
161
+ <div className="flex h-14 items-center justify-between px-4 border-b border-white/[0.06]">
162
+ {sidebarOpen && (
163
+ <span className="text-[10px] font-semibold tracking-widest uppercase text-white/90 leading-tight">
164
+ {getSiteConfig().name.split(" ")[0]}<br />
165
+ <span className="text-white/50 font-normal">Web Builder</span>
166
+ </span>
167
+ )}
168
+ <button
169
+ onClick={() => setSidebarOpen(!sidebarOpen)}
170
+ className="text-white/40 hover:text-white/70 transition-colors p-1 rounded-md hover:bg-white/[0.06]"
171
+ aria-label={sidebarOpen ? "Collapse sidebar" : "Expand sidebar"}
172
+ >
173
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
174
+ {sidebarOpen ? (
175
+ <polyline points="11 17 6 12 11 7" />
176
+ ) : (
177
+ <polyline points="13 7 18 12 13 17" />
178
+ )}
179
+ </svg>
180
+ </button>
181
+ </div>
182
+
183
+ {/* Nav links — flat list */}
184
+ <nav className="flex-1 py-3 px-2 overflow-y-auto">
185
+ {navLinks.map((link) => {
186
+ const isActive = isLinkActive(link.href);
187
+ return (
188
+ <Link
189
+ key={link.href}
190
+ href={link.href}
191
+ className={`flex items-center gap-3 px-3 py-2 rounded-lg text-sm transition-all mb-0.5 ${
192
+ isActive
193
+ ? "bg-white/[0.08] text-white font-medium"
194
+ : "text-white/60 hover:bg-white/[0.06] hover:text-white/90"
195
+ } ${!sidebarOpen ? "justify-center px-0" : ""}`}
196
+ title={!sidebarOpen ? link.label : undefined}
197
+ >
198
+ <NavIcon icon={link.icon} active={isActive} />
199
+ {sidebarOpen && (
200
+ <span className="text-[13px]">{link.label}</span>
201
+ )}
202
+ </Link>
203
+ );
204
+ })}
205
+ </nav>
206
+
207
+ {/* Setup Wizard */}
208
+ <div className="px-2 mb-0.5">
209
+ <Link
210
+ href="/admin/setup"
211
+ className={`flex items-center gap-3 text-white/40 hover:text-white/80 transition-colors px-3 py-2 rounded-lg hover:bg-white/[0.06] w-full text-sm ${
212
+ !sidebarOpen ? "justify-center px-0" : ""
213
+ }`}
214
+ title="Setup Wizard"
215
+ >
216
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
217
+ <path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76Z" />
218
+ </svg>
219
+ {sidebarOpen && <span className="text-[13px]">Setup Wizard</span>}
220
+ </Link>
221
+ </div>
222
+
223
+ {/* View Site */}
224
+ <div className="px-2 mb-1">
225
+ <Link
226
+ href="/"
227
+ target="_blank"
228
+ className={`flex items-center gap-3 text-white/40 hover:text-white/80 transition-colors px-3 py-2 rounded-lg hover:bg-white/[0.06] w-full text-sm ${
229
+ !sidebarOpen ? "justify-center px-0" : ""
230
+ }`}
231
+ title="View Site"
232
+ >
233
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
234
+ <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" />
235
+ <polyline points="15 3 21 3 21 9" />
236
+ <line x1="10" y1="14" x2="21" y2="3" />
237
+ </svg>
238
+ {sidebarOpen && <span className="text-[13px]">View Site</span>}
239
+ </Link>
240
+ </div>
241
+
242
+ {/* Logout */}
243
+ <div className="border-t border-white/[0.06] p-2">
244
+ <button
245
+ onClick={handleLogout}
246
+ className={`flex items-center gap-3 text-white/40 hover:text-red-400 transition-colors px-3 py-2 rounded-lg hover:bg-red-500/10 w-full text-sm ${
247
+ !sidebarOpen ? "justify-center px-0" : ""
248
+ }`}
249
+ title="Logout"
250
+ >
251
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
252
+ <path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" />
253
+ <polyline points="16 17 21 12 16 7" />
254
+ <line x1="21" y1="12" x2="9" y2="12" />
255
+ </svg>
256
+ {sidebarOpen && <span className="text-[13px]">Logout</span>}
257
+ </button>
258
+ </div>
259
+ </aside>
260
+
261
+ {/* Main content area — no top header bar, pages have their own titles */}
262
+ <div className="flex flex-1 flex-col overflow-hidden">
263
+ <main className={`flex-1 ${
264
+ isPageBuilder
265
+ ? "overflow-hidden"
266
+ : "overflow-y-auto p-8 bg-[#f8f8f8]"
267
+ }`}>
268
+ {children}
269
+ </main>
270
+ </div>
271
+ </div>
272
+ );
273
+ }