@morphika/andami 0.1.2

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 +50 -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 +212 -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,331 @@
1
+ "use client";
2
+
3
+ import { useState, useCallback, useEffect } from "react";
4
+ import { csrfHeaders } from "../../../lib/csrf-client";
5
+ import type { WizardStepProps } from "./SetupWizard";
6
+
7
+ // ── Icons ──
8
+
9
+ function CheckCircle() {
10
+ return (
11
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#22c55e" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
12
+ <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" />
13
+ <polyline points="22 4 12 14.01 9 11.01" />
14
+ </svg>
15
+ );
16
+ }
17
+
18
+ function XCircle() {
19
+ return (
20
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#ef4444" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
21
+ <circle cx="12" cy="12" r="10" />
22
+ <line x1="15" y1="9" x2="9" y2="15" />
23
+ <line x1="9" y1="9" x2="15" y2="15" />
24
+ </svg>
25
+ );
26
+ }
27
+
28
+ function Spinner() {
29
+ return (
30
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="animate-spin">
31
+ <path d="M21 12a9 9 0 1 1-6.219-8.56" />
32
+ </svg>
33
+ );
34
+ }
35
+
36
+ // ── Types ──
37
+
38
+ interface ConnectionResult {
39
+ connected: boolean;
40
+ projectId?: string;
41
+ dataset?: string;
42
+ hasWriteToken?: boolean;
43
+ stats?: {
44
+ siteSettings: number;
45
+ siteStyles: number;
46
+ assetRegistry: number;
47
+ };
48
+ error?: string;
49
+ }
50
+
51
+ interface SeedResult {
52
+ seeded: string[];
53
+ skipped: string[];
54
+ }
55
+
56
+ type ConnectionState = "idle" | "testing" | "connected" | "failed";
57
+ type SeedState = "idle" | "seeding" | "done" | "error";
58
+
59
+ // ── Component ──
60
+
61
+ /**
62
+ * Step 2 — Database (Sanity Connection)
63
+ *
64
+ * Tests the Sanity connection and seeds initial documents if needed.
65
+ */
66
+ export function DatabaseStep({ onNext, onBack }: WizardStepProps) {
67
+ const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID || "";
68
+ const hasProjectId = !!projectId;
69
+
70
+ const [connState, setConnState] = useState<ConnectionState>("idle");
71
+ const [connResult, setConnResult] = useState<ConnectionResult | null>(null);
72
+ const [seedState, setSeedState] = useState<SeedState>("idle");
73
+ const [seedResult, setSeedResult] = useState<SeedResult | null>(null);
74
+ const [error, setError] = useState<string>("");
75
+
76
+ // Auto-test on mount if project ID is present
77
+ useEffect(() => {
78
+ if (hasProjectId && connState === "idle") {
79
+ testConnection();
80
+ }
81
+ // eslint-disable-next-line react-hooks/exhaustive-deps
82
+ }, []);
83
+
84
+ const testConnection = useCallback(async () => {
85
+ setConnState("testing");
86
+ setError("");
87
+
88
+ try {
89
+ const res = await fetch("/api/admin/database");
90
+ if (!res.ok) {
91
+ setConnState("failed");
92
+ setError(`API returned ${res.status}`);
93
+ return;
94
+ }
95
+
96
+ const data: ConnectionResult = await res.json();
97
+ setConnResult(data);
98
+
99
+ if (data.connected) {
100
+ setConnState("connected");
101
+ } else {
102
+ setConnState("failed");
103
+ setError(data.error || "Connection failed");
104
+ }
105
+ } catch (err) {
106
+ setConnState("failed");
107
+ setError(err instanceof Error ? err.message : "Connection failed");
108
+ }
109
+ }, []);
110
+
111
+ const seedDocuments = useCallback(async () => {
112
+ setSeedState("seeding");
113
+
114
+ try {
115
+ const res = await fetch("/api/admin/setup", {
116
+ method: "POST",
117
+ headers: {
118
+ "Content-Type": "application/json",
119
+ ...csrfHeaders(),
120
+ },
121
+ });
122
+
123
+ if (!res.ok) {
124
+ const data = await res.json();
125
+ setSeedState("error");
126
+ setError(data.error || "Seeding failed");
127
+ return;
128
+ }
129
+
130
+ const data: SeedResult = await res.json();
131
+ setSeedResult(data);
132
+ setSeedState("done");
133
+ } catch (err) {
134
+ setSeedState("error");
135
+ setError(err instanceof Error ? err.message : "Seeding failed");
136
+ }
137
+ }, []);
138
+
139
+ // Should we show the seed button?
140
+ const needsSeeding =
141
+ connState === "connected" &&
142
+ connResult?.stats &&
143
+ (connResult.stats.siteSettings === 0 ||
144
+ connResult.stats.siteStyles === 0 ||
145
+ connResult.stats.assetRegistry === 0);
146
+
147
+ const docsPresent =
148
+ connState === "connected" &&
149
+ connResult?.stats &&
150
+ connResult.stats.siteSettings > 0 &&
151
+ connResult.stats.siteStyles > 0 &&
152
+ connResult.stats.assetRegistry > 0;
153
+
154
+ const allSeeded = seedState === "done" || (docsPresent && !needsSeeding);
155
+
156
+ const canAdvance = connState === "connected" && allSeeded;
157
+
158
+ return (
159
+ <div className="pt-8">
160
+ <h2 className="text-lg font-semibold text-[#111] mb-1">Database Connection</h2>
161
+ <p className="text-sm text-[#666] mb-8">
162
+ Connect to Sanity CMS to store your pages, settings, and content.
163
+ </p>
164
+
165
+ {/* Project ID status */}
166
+ <div className="bg-white rounded-xl border border-black/[0.06] divide-y divide-black/[0.06] mb-6">
167
+ {/* Sanity Project ID */}
168
+ <div className="flex items-center justify-between p-4">
169
+ <div>
170
+ <span className="text-sm font-medium text-[#333]">Sanity Project ID</span>
171
+ {hasProjectId && (
172
+ <p className="text-xs text-[#999] mt-0.5 font-mono">{projectId}</p>
173
+ )}
174
+ </div>
175
+ {hasProjectId ? <CheckCircle /> : <XCircle />}
176
+ </div>
177
+
178
+ {/* Connection status */}
179
+ <div className="flex items-center justify-between p-4">
180
+ <div>
181
+ <span className="text-sm font-medium text-[#333]">Connection</span>
182
+ {connState === "connected" && connResult?.dataset && (
183
+ <p className="text-xs text-[#999] mt-0.5">
184
+ Dataset: <span className="font-mono">{connResult.dataset}</span>
185
+ </p>
186
+ )}
187
+ </div>
188
+ {connState === "testing" && <Spinner />}
189
+ {connState === "connected" && <CheckCircle />}
190
+ {connState === "failed" && <XCircle />}
191
+ {connState === "idle" && (
192
+ <span className="text-xs text-[#bbb]">Not tested</span>
193
+ )}
194
+ </div>
195
+
196
+ {/* Write token */}
197
+ {connState === "connected" && (
198
+ <div className="flex items-center justify-between p-4">
199
+ <div>
200
+ <span className="text-sm font-medium text-[#333]">Write Token</span>
201
+ <p className="text-xs text-[#999] mt-0.5">Required for saving content</p>
202
+ </div>
203
+ {connResult?.hasWriteToken ? <CheckCircle /> : <XCircle />}
204
+ </div>
205
+ )}
206
+
207
+ {/* Document seeding */}
208
+ {connState === "connected" && (
209
+ <div className="flex items-center justify-between p-4">
210
+ <div>
211
+ <span className="text-sm font-medium text-[#333]">Initial Documents</span>
212
+ {seedState === "done" && seedResult && (
213
+ <p className="text-xs text-[#999] mt-0.5">
214
+ {seedResult.seeded.length > 0
215
+ ? `Created: ${seedResult.seeded.join(", ")}`
216
+ : "All documents already exist"}
217
+ </p>
218
+ )}
219
+ {allSeeded && seedState !== "done" && (
220
+ <p className="text-xs text-[#999] mt-0.5">All documents present</p>
221
+ )}
222
+ </div>
223
+ {seedState === "seeding" && <Spinner />}
224
+ {(seedState === "done" || allSeeded) && <CheckCircle />}
225
+ {seedState === "error" && <XCircle />}
226
+ {seedState === "idle" && !allSeeded && (
227
+ <span className="text-xs text-[#bbb]">Not seeded</span>
228
+ )}
229
+ </div>
230
+ )}
231
+ </div>
232
+
233
+ {/* Instructions when no project ID */}
234
+ {!hasProjectId && (
235
+ <div className="bg-amber-50 border border-amber-200 rounded-lg p-4 mb-6">
236
+ <p className="text-sm text-amber-800 font-medium mb-2">
237
+ Sanity Project ID not found
238
+ </p>
239
+ <ol className="text-xs text-amber-700 space-y-1.5 list-decimal list-inside">
240
+ <li>
241
+ Create a project at{" "}
242
+ <a
243
+ href="https://www.sanity.io/manage"
244
+ target="_blank"
245
+ rel="noopener noreferrer"
246
+ className="underline hover:text-amber-900"
247
+ >
248
+ sanity.io/manage
249
+ </a>
250
+ </li>
251
+ <li>
252
+ Add <code className="bg-amber-100 px-1 rounded font-mono">NEXT_PUBLIC_SANITY_PROJECT_ID=your-id</code> to{" "}
253
+ <code className="bg-amber-100 px-1 rounded font-mono">.env.local</code>
254
+ </li>
255
+ <li>
256
+ Add <code className="bg-amber-100 px-1 rounded font-mono">SANITY_API_TOKEN=your-token</code> (Editor or higher)
257
+ </li>
258
+ <li>Restart the dev server and refresh this page</li>
259
+ </ol>
260
+ </div>
261
+ )}
262
+
263
+ {/* Error message */}
264
+ {error && connState === "failed" && (
265
+ <div className="bg-red-50 border border-red-200 rounded-lg p-3 mb-6">
266
+ <p className="text-xs text-red-700">{error}</p>
267
+ </div>
268
+ )}
269
+
270
+ {/* Write token warning */}
271
+ {connState === "connected" && !connResult?.hasWriteToken && (
272
+ <div className="bg-amber-50 border border-amber-200 rounded-lg p-3 mb-6">
273
+ <p className="text-xs text-amber-700">
274
+ <span className="font-medium">Write token missing.</span> Add{" "}
275
+ <code className="bg-amber-100 px-1 rounded font-mono">SANITY_API_TOKEN</code> to{" "}
276
+ <code className="bg-amber-100 px-1 rounded font-mono">.env.local</code> and
277
+ restart the dev server. Without it, content changes won&apos;t be saved.
278
+ </p>
279
+ </div>
280
+ )}
281
+
282
+ {/* Actions */}
283
+ <div className="flex items-center justify-between">
284
+ <div>
285
+ {onBack && (
286
+ <button
287
+ onClick={onBack}
288
+ className="px-4 py-2 text-sm text-[#666] hover:text-[#333] transition-colors"
289
+ >
290
+ Back
291
+ </button>
292
+ )}
293
+ </div>
294
+
295
+ <div className="flex items-center gap-3">
296
+ {/* Test Connection */}
297
+ {hasProjectId && connState !== "connected" && (
298
+ <button
299
+ onClick={testConnection}
300
+ disabled={connState === "testing"}
301
+ className="px-4 py-2 text-sm border border-black/[0.1] rounded-lg hover:bg-black/[0.02] transition-colors disabled:opacity-50"
302
+ >
303
+ {connState === "testing" ? "Testing..." : "Test Connection"}
304
+ </button>
305
+ )}
306
+
307
+ {/* Seed Documents */}
308
+ {needsSeeding && seedState !== "done" && connResult?.hasWriteToken && (
309
+ <button
310
+ onClick={seedDocuments}
311
+ disabled={seedState === "seeding"}
312
+ className="px-4 py-2 text-sm bg-[#076bff] text-white rounded-lg hover:bg-[#0559d4] transition-colors disabled:opacity-50"
313
+ >
314
+ {seedState === "seeding" ? "Creating..." : "Create Initial Documents"}
315
+ </button>
316
+ )}
317
+
318
+ {/* Next */}
319
+ {canAdvance && (
320
+ <button
321
+ onClick={onNext}
322
+ className="px-5 py-2.5 bg-[#076bff] text-white text-sm font-medium rounded-lg hover:bg-[#0559d4] transition-colors"
323
+ >
324
+ Next
325
+ </button>
326
+ )}
327
+ </div>
328
+ </div>
329
+ </div>
330
+ );
331
+ }
@@ -0,0 +1,187 @@
1
+ "use client";
2
+
3
+ import { useState, useCallback, useEffect } from "react";
4
+ import { useRouter } from "next/navigation";
5
+ import { csrfHeaders } from "../../../lib/csrf-client";
6
+ import type { WizardStepProps } from "./SetupWizard";
7
+
8
+ // ── Icons ──
9
+
10
+ function CheckCircle({ color = "#22c55e" }: { color?: string }) {
11
+ return (
12
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
13
+ <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" />
14
+ <polyline points="22 4 12 14.01 9 11.01" />
15
+ </svg>
16
+ );
17
+ }
18
+
19
+ function SkipIcon() {
20
+ return (
21
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#bbb" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
22
+ <circle cx="12" cy="12" r="10" />
23
+ <line x1="8" y1="12" x2="16" y2="12" />
24
+ </svg>
25
+ );
26
+ }
27
+
28
+ // ── Types ──
29
+
30
+ interface StepSummary {
31
+ label: string;
32
+ configured: boolean;
33
+ }
34
+
35
+ // ── Component ──
36
+
37
+ /**
38
+ * Step 5 — Done
39
+ *
40
+ * Shows a summary of what was configured, then marks setup as complete
41
+ * and redirects to the admin pages.
42
+ */
43
+ export function DoneStep({ onBack }: WizardStepProps) {
44
+ const router = useRouter();
45
+ const [steps, setSteps] = useState<StepSummary[]>([]);
46
+ const [loading, setLoading] = useState(true);
47
+ const [completing, setCompleting] = useState(false);
48
+ const [error, setError] = useState<string | null>(null);
49
+
50
+ // Fetch current status to show summary
51
+ useEffect(() => {
52
+ fetch("/api/admin/setup")
53
+ .then((res) => (res.ok ? res.json() : null))
54
+ .then((data) => {
55
+ if (data?.steps) {
56
+ setSteps([
57
+ { label: "Database", configured: data.steps.database },
58
+ { label: "Storage", configured: data.steps.storage },
59
+ { label: "Branding", configured: data.steps.branding },
60
+ ]);
61
+ }
62
+ setLoading(false);
63
+ })
64
+ .catch(() => setLoading(false));
65
+ }, []);
66
+
67
+ const handleComplete = useCallback(async () => {
68
+ setCompleting(true);
69
+ setError(null);
70
+
71
+ try {
72
+ // Mark setup as complete via the setup API
73
+ const res = await fetch("/api/admin/setup/complete", {
74
+ method: "POST",
75
+ headers: {
76
+ "Content-Type": "application/json",
77
+ ...csrfHeaders(),
78
+ },
79
+ });
80
+
81
+ if (!res.ok) {
82
+ const data = await res.json();
83
+ setError(data.error || "Failed to complete setup");
84
+ setCompleting(false);
85
+ return;
86
+ }
87
+
88
+ // Redirect to admin pages
89
+ router.push("/admin/pages");
90
+ router.refresh();
91
+ } catch (err) {
92
+ setError(err instanceof Error ? err.message : "Failed to complete setup");
93
+ setCompleting(false);
94
+ }
95
+ }, [router]);
96
+
97
+ return (
98
+ <div className="pt-8">
99
+ <div className="text-center mb-8">
100
+ <div className="w-12 h-12 rounded-full bg-green-100 flex items-center justify-center mx-auto mb-4">
101
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#22c55e" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
102
+ <polyline points="20 6 9 17 4 12" />
103
+ </svg>
104
+ </div>
105
+ <h2 className="text-lg font-semibold text-[#111] mb-1">
106
+ You&apos;re all set!
107
+ </h2>
108
+ <p className="text-sm text-[#666]">
109
+ Here&apos;s a summary of your setup. You can reconfigure any of these later.
110
+ </p>
111
+ </div>
112
+
113
+ {/* Summary */}
114
+ {!loading && steps.length > 0 && (
115
+ <div className="bg-white rounded-xl border border-black/[0.06] divide-y divide-black/[0.06] mb-6">
116
+ {steps.map((step) => (
117
+ <div key={step.label} className="flex items-center justify-between p-4">
118
+ <span className="text-sm font-medium text-[#333]">
119
+ {step.label}
120
+ </span>
121
+ <div className="flex items-center gap-2">
122
+ {step.configured ? (
123
+ <>
124
+ <CheckCircle />
125
+ <span className="text-xs text-green-600 font-medium">
126
+ Configured
127
+ </span>
128
+ </>
129
+ ) : (
130
+ <>
131
+ <SkipIcon />
132
+ <span className="text-xs text-[#bbb]">Skipped</span>
133
+ </>
134
+ )}
135
+ </div>
136
+ </div>
137
+ ))}
138
+ </div>
139
+ )}
140
+
141
+ {/* Loading state for summary */}
142
+ {loading && (
143
+ <div className="bg-white rounded-xl border border-black/[0.06] p-8 mb-6 text-center">
144
+ <p className="text-xs text-[#999] animate-pulse">
145
+ Loading summary...
146
+ </p>
147
+ </div>
148
+ )}
149
+
150
+ {/* Tip */}
151
+ <div className="p-3 rounded-lg bg-[#f8f8f8] border border-black/[0.04] mb-6">
152
+ <p className="text-xs text-[#999] leading-relaxed">
153
+ You can re-run this wizard anytime from the admin sidebar.
154
+ Skipped steps can be configured from the corresponding admin pages.
155
+ </p>
156
+ </div>
157
+
158
+ {error && (
159
+ <div className="p-3 rounded-lg bg-red-50 border border-red-200 mb-6">
160
+ <p className="text-xs text-red-700">{error}</p>
161
+ </div>
162
+ )}
163
+
164
+ {/* Actions */}
165
+ <div className="flex items-center justify-between">
166
+ <div>
167
+ {onBack && (
168
+ <button
169
+ onClick={onBack}
170
+ className="px-4 py-2 text-sm text-[#666] hover:text-[#333] transition-colors"
171
+ >
172
+ Back
173
+ </button>
174
+ )}
175
+ </div>
176
+
177
+ <button
178
+ onClick={handleComplete}
179
+ disabled={completing}
180
+ className="px-6 py-2.5 bg-[#076bff] text-white text-sm font-medium rounded-lg hover:bg-[#0559d4] transition-colors disabled:opacity-50"
181
+ >
182
+ {completing ? "Starting..." : "Start Building"}
183
+ </button>
184
+ </div>
185
+ </div>
186
+ );
187
+ }
@@ -0,0 +1,166 @@
1
+ "use client";
2
+
3
+ import { useState, useCallback } from "react";
4
+ import { useRouter } from "next/navigation";
5
+ import { getSiteConfig } from "../../../lib/config";
6
+ import type { SetupStatus } from "../../../lib/setup/detect";
7
+ import { WelcomeStep } from "./WelcomeStep";
8
+ import { DatabaseStep } from "./DatabaseStep";
9
+ import { StorageStep } from "./StorageStep";
10
+ import { BrandingStep } from "./BrandingStep";
11
+ import { DoneStep } from "./DoneStep";
12
+
13
+ // ── Types ──
14
+
15
+ export interface WizardStepProps {
16
+ onNext: () => void;
17
+ onBack?: () => void;
18
+ }
19
+
20
+ interface StepDef {
21
+ id: string;
22
+ label: string;
23
+ component: React.ComponentType<WizardStepProps>;
24
+ }
25
+
26
+ const STEPS: StepDef[] = [
27
+ { id: "welcome", label: "Welcome", component: WelcomeStep },
28
+ { id: "database", label: "Database", component: DatabaseStep },
29
+ { id: "storage", label: "Storage", component: StorageStep },
30
+ { id: "branding", label: "Branding", component: BrandingStep },
31
+ { id: "done", label: "Done", component: DoneStep },
32
+ ];
33
+
34
+ // ── Main Wizard ──
35
+
36
+ interface SetupWizardProps {
37
+ initialStatus: SetupStatus;
38
+ }
39
+
40
+ export function SetupWizard({ initialStatus }: SetupWizardProps) {
41
+ const router = useRouter();
42
+ const config = getSiteConfig();
43
+
44
+ // Determine initial step based on what's already configured
45
+ const getInitialStep = (): number => {
46
+ if (!initialStatus.steps.database) return 0; // Welcome → Database
47
+ if (!initialStatus.steps.storage) return 2; // Jump to Storage
48
+ if (!initialStatus.steps.branding) return 3; // Jump to Branding
49
+ return 0; // All done — shouldn't be here, but start at Welcome
50
+ };
51
+
52
+ const [currentStep, setCurrentStep] = useState(getInitialStep);
53
+
54
+ const handleNext = useCallback(() => {
55
+ if (currentStep < STEPS.length - 1) {
56
+ setCurrentStep((s) => s + 1);
57
+ } else {
58
+ // Final step — redirect to admin
59
+ router.push("/admin/pages");
60
+ router.refresh();
61
+ }
62
+ }, [currentStep, router]);
63
+
64
+ const handleBack = useCallback(() => {
65
+ if (currentStep > 0) {
66
+ setCurrentStep((s) => s - 1);
67
+ }
68
+ }, [currentStep]);
69
+
70
+ const handleSkip = useCallback(() => {
71
+ router.push("/admin/pages");
72
+ router.refresh();
73
+ }, [router]);
74
+
75
+ const StepComponent = STEPS[currentStep].component;
76
+
77
+ return (
78
+ <div
79
+ className="min-h-screen bg-[#f8f8f8] flex flex-col"
80
+ style={{ fontFamily: "Inter, system-ui, sans-serif" }}
81
+ >
82
+ {/* Top bar */}
83
+ <header className="flex items-center justify-between px-8 py-4 border-b border-black/[0.06]">
84
+ <span className="text-xs font-semibold tracking-widest uppercase text-[#333]">
85
+ {config.name} <span className="text-[#999] font-normal">Setup</span>
86
+ </span>
87
+ <button
88
+ onClick={handleSkip}
89
+ className="text-xs text-[#999] hover:text-[#333] transition-colors"
90
+ >
91
+ Skip for now
92
+ </button>
93
+ </header>
94
+
95
+ {/* Stepper */}
96
+ <div className="flex justify-center pt-8 pb-4 px-8">
97
+ <div className="flex items-center gap-2">
98
+ {STEPS.map((step, i) => {
99
+ const isActive = i === currentStep;
100
+ const isDone = i < currentStep;
101
+ return (
102
+ <div key={step.id} className="flex items-center gap-2">
103
+ {/* Step dot + label */}
104
+ <div className="flex items-center gap-1.5">
105
+ <div
106
+ className={`w-6 h-6 rounded-full flex items-center justify-center text-[10px] font-semibold transition-colors ${
107
+ isActive
108
+ ? "bg-[#076bff] text-white"
109
+ : isDone
110
+ ? "bg-[#076bff]/20 text-[#076bff]"
111
+ : "bg-black/[0.06] text-[#999]"
112
+ }`}
113
+ >
114
+ {isDone ? (
115
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round">
116
+ <polyline points="20 6 9 17 4 12" />
117
+ </svg>
118
+ ) : (
119
+ i + 1
120
+ )}
121
+ </div>
122
+ <span
123
+ className={`text-xs transition-colors ${
124
+ isActive
125
+ ? "text-[#333] font-medium"
126
+ : isDone
127
+ ? "text-[#076bff]"
128
+ : "text-[#999]"
129
+ }`}
130
+ >
131
+ {step.label}
132
+ </span>
133
+ </div>
134
+ {/* Connector line */}
135
+ {i < STEPS.length - 1 && (
136
+ <div
137
+ className={`w-8 h-px transition-colors ${
138
+ i < currentStep ? "bg-[#076bff]/30" : "bg-black/[0.08]"
139
+ }`}
140
+ />
141
+ )}
142
+ </div>
143
+ );
144
+ })}
145
+ </div>
146
+ </div>
147
+
148
+ {/* Step content */}
149
+ <div className="flex-1 flex justify-center px-8 pb-12">
150
+ <div className="w-full max-w-lg">
151
+ <StepComponent
152
+ onNext={handleNext}
153
+ onBack={currentStep > 0 ? handleBack : undefined}
154
+ />
155
+ </div>
156
+ </div>
157
+
158
+ {/* Footer */}
159
+ <footer className="px-8 py-4 border-t border-black/[0.06] text-center">
160
+ <span className="text-[11px] text-[#bbb]">
161
+ Step {currentStep + 1} of {STEPS.length}
162
+ </span>
163
+ </footer>
164
+ </div>
165
+ );
166
+ }