@morphika/andami 0.5.1 → 0.5.3
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.
- package/README.md +27 -2
- package/app/admin/assets/page.tsx +6 -6
- package/app/admin/database/page.tsx +302 -302
- package/app/admin/error.tsx +53 -53
- package/app/admin/layout.tsx +332 -320
- package/app/admin/navigation/page.tsx +255 -255
- package/app/admin/pages/[slug]/page.tsx +44 -27
- package/app/admin/pages/page.tsx +24 -19
- package/app/admin/projects/page.tsx +30 -21
- package/app/admin/setup/page.tsx +1 -1
- package/app/admin/styles/page.tsx +1 -1
- package/app/api/admin/assets/register/route.ts +51 -14
- package/app/api/admin/assets/registry/route.ts +4 -1
- package/app/api/admin/assets/relink/confirm/route.ts +4 -1
- package/app/api/admin/assets/relink/route.ts +4 -1
- package/app/api/admin/assets/scan/route.ts +4 -1
- package/app/api/admin/backups/restore-data/route.ts +4 -1
- package/app/api/admin/r2/connect/route.ts +4 -1
- package/app/api/admin/r2/delete/route.ts +4 -1
- package/app/api/admin/r2/rename/route.ts +4 -1
- package/app/api/admin/r2/upload-url/route.ts +4 -1
- package/app/api/admin/revalidate/route.ts +4 -1
- package/app/api/admin/storage/switch/route.ts +4 -1
- package/app/api/custom-sections/[id]/route.ts +5 -6
- package/components/admin/MetadataEditor.tsx +6 -6
- package/components/admin/PublishToggle.tsx +2 -2
- package/components/admin/nav-builder/NavBuilder.tsx +1 -1
- package/components/admin/nav-builder/NavBuilderGrid.tsx +3 -3
- package/components/admin/nav-builder/NavGridCell.tsx +48 -48
- package/components/admin/nav-builder/NavGridItem.tsx +8 -6
- package/components/admin/nav-builder/NavItemSettings.tsx +331 -331
- package/components/admin/nav-builder/NavItemTypePicker.tsx +102 -102
- package/components/admin/nav-builder/NavLivePreview.tsx +1 -1
- package/components/admin/nav-builder/NavMobileLivePreview.tsx +226 -226
- package/components/admin/nav-builder/NavMobileSettings.tsx +242 -242
- package/components/admin/nav-builder/NavSettingsFields.tsx +518 -514
- package/components/admin/setup-wizard/BrandingStep.tsx +3 -3
- package/components/admin/setup-wizard/DatabaseStep.tsx +2 -2
- package/components/admin/setup-wizard/DoneStep.tsx +1 -1
- package/components/admin/setup-wizard/SetupWizard.tsx +4 -4
- package/components/admin/setup-wizard/StorageStep.tsx +2 -2
- package/components/admin/setup-wizard/WelcomeStep.tsx +2 -2
- package/components/admin/styles/ColorsEditor.tsx +9 -8
- package/components/admin/styles/FontsEditor.tsx +9 -7
- package/components/admin/styles/GridLayoutEditor.tsx +9 -9
- package/components/admin/styles/LinksButtonsEditor.tsx +5 -5
- package/components/admin/styles/TypographyEditor.tsx +6 -6
- package/components/admin/styles/shared.tsx +68 -68
- package/components/blocks/AudioBlockRenderer.tsx +286 -286
- package/components/blocks/CoverSectionRenderer.tsx +7 -1
- package/components/blocks/MarqueeBlockRenderer.tsx +316 -0
- package/components/blocks/ProjectCarouselBlockRenderer.tsx +1 -1
- package/components/blocks/SectionV2Renderer.tsx +8 -1
- package/components/builder/BlockCardIcons.tsx +316 -316
- package/components/builder/BlockTypePicker.tsx +1 -1
- package/components/builder/BubbleIcons.tsx +104 -0
- package/components/builder/BuilderCanvas.tsx +2 -0
- package/components/builder/CanvasMinimap.tsx +66 -49
- package/components/builder/CanvasToolbar.tsx +31 -41
- package/components/builder/CoverSectionCanvas.tsx +363 -363
- package/components/builder/DeviceFrame.tsx +1 -1
- package/components/builder/DndWrapper.tsx +3 -3
- package/components/builder/InsertionLines.tsx +1 -1
- package/components/builder/SectionCardIcons.tsx +421 -320
- package/components/builder/SectionEditorBar.tsx +5 -3
- package/components/builder/SectionTypePicker.tsx +7 -5
- package/components/builder/SectionV2Canvas.tsx +1 -1
- package/components/builder/SectionV2Column.tsx +82 -68
- package/components/builder/SettingsPanel.tsx +21 -17
- package/components/builder/SortableBlock.tsx +93 -73
- package/components/builder/SortableRow.tsx +33 -35
- package/components/builder/VirtualAssetGrid.tsx +10 -4
- package/components/builder/asset-browser/R2BrowserContent.tsx +18 -14
- package/components/builder/blockStyles.tsx +192 -185
- package/components/builder/color-picker/AlphaSlider.tsx +141 -141
- package/components/builder/color-picker/ColorInputs.tsx +105 -105
- package/components/builder/color-picker/EyedropperButton.tsx +75 -74
- package/components/builder/color-picker/HueSlider.tsx +124 -124
- package/components/builder/color-picker/SaturationCanvas.tsx +142 -142
- package/components/builder/color-picker/SwatchBar.tsx +98 -93
- package/components/builder/color-picker/UnifiedColorPicker.tsx +11 -6
- package/components/builder/editors/AudioBlockEditor.tsx +242 -242
- package/components/builder/editors/BeforeAfterBlockEditor.tsx +360 -360
- package/components/builder/editors/ButtonBlockEditor.tsx +4 -4
- package/components/builder/editors/EnterAnimationPicker.tsx +2 -2
- package/components/builder/editors/HoverEffectPicker.tsx +2 -2
- package/components/builder/editors/ImageBlockEditor.tsx +2 -2
- package/components/builder/editors/ImageGridBlockEditor.tsx +8 -6
- package/components/builder/editors/MarqueeBlockEditor.tsx +622 -0
- package/components/builder/editors/ProjectCarouselBlockEditor.tsx +443 -443
- package/components/builder/editors/ProjectGridEditor.tsx +21 -16
- package/components/builder/editors/SpacerBlockEditor.tsx +29 -27
- package/components/builder/editors/StaggerSettings.tsx +109 -109
- package/components/builder/editors/TextBlockEditor.tsx +22 -17
- package/components/builder/editors/TextStylePicker.tsx +1 -1
- package/components/builder/editors/VideoBlockEditor.tsx +2 -2
- package/components/builder/editors/index.ts +11 -10
- package/components/builder/editors/shared.tsx +10 -8
- package/components/builder/live-preview/LiveAudioPreview.tsx +120 -120
- package/components/builder/live-preview/LiveBeforeAfterPreview.tsx +1 -1
- package/components/builder/live-preview/LiveImageGridPreview.tsx +10 -2
- package/components/builder/live-preview/LiveImagePreview.tsx +4 -2
- package/components/builder/live-preview/LiveMarqueePreview.tsx +39 -0
- package/components/builder/live-preview/LiveProjectCarouselPreview.tsx +1 -1
- package/components/builder/live-preview/LiveVideoPreview.tsx +1 -1
- package/components/builder/live-preview/ProjectCardWrapper.tsx +293 -291
- package/components/builder/live-preview/RichTextBubbleMenu.tsx +10 -6
- package/components/builder/live-preview/shared.tsx +5 -2
- package/components/builder/settings-panel/AnimationTab.tsx +138 -138
- package/components/builder/settings-panel/BlockLayoutTab.tsx +11 -9
- package/components/builder/settings-panel/CardEntranceSection.tsx +114 -114
- package/components/builder/settings-panel/ColumnV2LayoutTab.tsx +242 -0
- package/components/builder/settings-panel/ColumnV2Settings.tsx +5 -5
- package/components/builder/settings-panel/CoverSectionLayoutTab.tsx +71 -71
- package/components/builder/settings-panel/CoverSectionSettings.tsx +337 -335
- package/components/builder/settings-panel/PageSettings.tsx +3 -3
- package/components/builder/settings-panel/ParallaxSlideSettings.tsx +2 -2
- package/components/builder/settings-panel/SectionV2AnimationTab.tsx +4 -4
- package/components/builder/settings-panel/SectionV2LayoutTab.tsx +356 -356
- package/components/builder/settings-panel/SectionV2Settings.tsx +25 -20
- package/components/builder/settings-panel/TRBLInputs.tsx +1 -1
- package/components/builder/settings-panel/index.ts +1 -0
- package/lib/animation/enter-types.ts +1 -0
- package/lib/animation/hover-effect-presets.ts +210 -210
- package/lib/animation/hover-effect-types.ts +1 -0
- package/lib/builder/block-registrations.ts +468 -417
- package/lib/builder/constants.ts +111 -111
- package/lib/builder/serializer/normalizers.ts +14 -0
- package/lib/builder/serializer/serializers.ts +27 -0
- package/lib/builder/store-sections.ts +23 -2
- package/lib/builder/types-slices.ts +428 -414
- package/lib/builder/types.ts +4 -1
- package/lib/config/index.ts +27 -27
- package/lib/sanity/queries.ts +48 -0
- package/lib/sanity/types.ts +112 -1
- package/lib/version.ts +1 -1
- package/package.json +7 -5
- package/sanity/schemas/blocks/audioBlock.ts +69 -69
- package/sanity/schemas/blocks/index.ts +12 -11
- package/sanity/schemas/blocks/marqueeBlock.ts +292 -0
- package/sanity/schemas/index.ts +120 -117
- package/sanity/schemas/objects/coverSection.ts +32 -0
- package/sanity/schemas/objects/parallaxSlide.ts +32 -0
- package/sanity/schemas/pageSectionV2.ts +32 -0
- package/styles/admin.css +85 -85
- package/styles/animations.css +237 -237
- package/styles/base.css +114 -114
package/app/admin/layout.tsx
CHANGED
|
@@ -1,320 +1,332 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { usePathname, useRouter } from "next/navigation";
|
|
4
|
-
import Link from "next/link";
|
|
5
|
-
import { getSiteConfig } from "../../lib/config";
|
|
6
|
-
import { ANDAMI_VERSION } from "../../lib/version";
|
|
7
|
-
|
|
8
|
-
// ============================================
|
|
9
|
-
// Navigation Configuration — grouped by section
|
|
10
|
-
// ============================================
|
|
11
|
-
|
|
12
|
-
const workspaceLinks = [
|
|
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
|
-
];
|
|
18
|
-
|
|
19
|
-
const systemLinks = [
|
|
20
|
-
{ href: "/admin/storage", label: "Storage", icon: "harddisk" },
|
|
21
|
-
{ href: "/admin/database", label: "Database", icon: "database" },
|
|
22
|
-
{ href: "/admin/settings", label: "Metadata", icon: "code" },
|
|
23
|
-
{ href: "/admin/backups", label: "Backups", icon: "cloud-download" },
|
|
24
|
-
];
|
|
25
|
-
|
|
26
|
-
// Shared tile shape — logo square, active item, hover bg all share this
|
|
27
|
-
// so every piece lines up pixel-perfect.
|
|
28
|
-
const TILE_SIZE = "w-10 h-10";
|
|
29
|
-
const TILE_RADIUS = "rounded-lg";
|
|
30
|
-
|
|
31
|
-
// ============================================
|
|
32
|
-
// Tooltip — CSS-only (group-hover), no JS state, 120ms fade + slide-in
|
|
33
|
-
// ============================================
|
|
34
|
-
// Rendered to the right of its parent tile. The parent must have
|
|
35
|
-
// `group relative` classes (already baked into `tileBase`). Uses
|
|
36
|
-
// `pointer-events-none` so it never blocks clicks or creates its own
|
|
37
|
-
// hover state.
|
|
38
|
-
|
|
39
|
-
function Tooltip({ children }: { children: React.ReactNode }) {
|
|
40
|
-
return (
|
|
41
|
-
<span
|
|
42
|
-
role="tooltip"
|
|
43
|
-
className="pointer-events-none absolute left-full top-1/2 ml-1.5 z-50 whitespace-nowrap rounded-md border border-white/10 bg-[#2a2d33] px-2.5 py-1.5 text-[11px] font-medium text-white shadow-lg opacity-0 transition-[opacity,margin] duration-150 ease-out group-hover:opacity-100 group-hover:ml-3"
|
|
44
|
-
style={{ transform: "translateY(-50%)" }}
|
|
45
|
-
>
|
|
46
|
-
{children}
|
|
47
|
-
</span>
|
|
48
|
-
);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// ============================================
|
|
52
|
-
// Andami Mark — inlined from docs/andami_mark.svg
|
|
53
|
-
// ============================================
|
|
54
|
-
|
|
55
|
-
function AndamiMark({ size = 30 }: { size?: number }) {
|
|
56
|
-
// viewBox cropped to the actual content bbox (the original 0 0 500 500
|
|
57
|
-
// had ~36% empty padding that made the mark render tiny). Centered on the
|
|
58
|
-
// shape's true centroid (249.75, 240.2) with a 280px padded square.
|
|
59
|
-
return (
|
|
60
|
-
<svg
|
|
61
|
-
width={size}
|
|
62
|
-
height={size}
|
|
63
|
-
viewBox="110 100 280 280"
|
|
64
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
65
|
-
aria-hidden
|
|
66
|
-
>
|
|
67
|
-
<path
|
|
68
|
-
fill="#
|
|
69
|
-
d="M228.8,166.3h39.6c3.1,0,5.7-2.5,5.7-5.6V121c0-3.1-2.6-5.7-5.7-5.7h-39.6c-3.1,0-5.7,2.6-5.7,5.7v39.6 C223.1,163.8,225.7,166.3,228.8,166.3z"
|
|
70
|
-
/>
|
|
71
|
-
<path
|
|
72
|
-
fill="#1E2025"
|
|
73
|
-
d="M227.2,185.8h-39.6c-3.1,0-5.7,2.6-5.7,5.7v39.6c0,3.1,2.6,5.7,5.7,5.7h39.6c3.1,0,5.7-2.6,5.7-5.7v-39.6 C232.9,188.4,230.3,185.8,227.2,185.8z"
|
|
74
|
-
/>
|
|
75
|
-
<path
|
|
76
|
-
fill="#1E2025"
|
|
77
|
-
d="M311.4,231.1v-39.6c0-3.1-2.6-5.7-5.7-5.7h-39.6c-3.1,0-5.7,2.6-5.7,5.7v39.6c0,3.1,2.6,5.7,5.7,5.7h39.6 C308.8,236.8,311.4,234.3,311.4,231.1z"
|
|
78
|
-
/>
|
|
79
|
-
<path
|
|
80
|
-
fill="#1E2025"
|
|
81
|
-
d="M332.7,258.4h-41.3c-3.3,0-6,2.7-6,6v94.7c0,3.3,2.7,6,6,6h41.3c3.3,0,6-2.7,6-6v-94.7 C338.7,261.1,336,258.4,332.7,258.4z"
|
|
82
|
-
/>
|
|
83
|
-
<path
|
|
84
|
-
fill="#1E2025"
|
|
85
|
-
d="M208.1,258.4h-41.3c-3.3,0-6,2.7-6,6v94.7c0,3.3,2.7,6,6,6h41.3c3.3,0,6-2.7,6-6v-94.7 C214.2,261.1,211.5,258.4,208.1,258.4z"
|
|
86
|
-
/>
|
|
87
|
-
</svg>
|
|
88
|
-
);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// ============================================
|
|
92
|
-
// Icon Component — Mockup A set (Storage kept from original)
|
|
93
|
-
// ============================================
|
|
94
|
-
|
|
95
|
-
function NavIcon({ icon }: { icon: string }) {
|
|
96
|
-
const size = 20;
|
|
97
|
-
switch (icon) {
|
|
98
|
-
case "file":
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
<
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
<
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
<
|
|
127
|
-
<
|
|
128
|
-
<
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
<
|
|
136
|
-
<
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
<
|
|
144
|
-
<
|
|
145
|
-
<
|
|
146
|
-
</svg>
|
|
147
|
-
);
|
|
148
|
-
case "
|
|
149
|
-
return (
|
|
150
|
-
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
151
|
-
<
|
|
152
|
-
<
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
<path d="
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { usePathname, useRouter } from "next/navigation";
|
|
4
|
+
import Link from "next/link";
|
|
5
|
+
import { getSiteConfig } from "../../lib/config";
|
|
6
|
+
import { ANDAMI_VERSION } from "../../lib/version";
|
|
7
|
+
|
|
8
|
+
// ============================================
|
|
9
|
+
// Navigation Configuration — grouped by section
|
|
10
|
+
// ============================================
|
|
11
|
+
|
|
12
|
+
const workspaceLinks = [
|
|
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
|
+
];
|
|
18
|
+
|
|
19
|
+
const systemLinks = [
|
|
20
|
+
{ href: "/admin/storage", label: "Storage", icon: "harddisk" },
|
|
21
|
+
{ href: "/admin/database", label: "Database", icon: "database" },
|
|
22
|
+
{ href: "/admin/settings", label: "Metadata", icon: "code" },
|
|
23
|
+
{ href: "/admin/backups", label: "Backups", icon: "cloud-download" },
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
// Shared tile shape — logo square, active item, hover bg all share this
|
|
27
|
+
// so every piece lines up pixel-perfect.
|
|
28
|
+
const TILE_SIZE = "w-10 h-10";
|
|
29
|
+
const TILE_RADIUS = "rounded-lg";
|
|
30
|
+
|
|
31
|
+
// ============================================
|
|
32
|
+
// Tooltip — CSS-only (group-hover), no JS state, 120ms fade + slide-in
|
|
33
|
+
// ============================================
|
|
34
|
+
// Rendered to the right of its parent tile. The parent must have
|
|
35
|
+
// `group relative` classes (already baked into `tileBase`). Uses
|
|
36
|
+
// `pointer-events-none` so it never blocks clicks or creates its own
|
|
37
|
+
// hover state.
|
|
38
|
+
|
|
39
|
+
function Tooltip({ children }: { children: React.ReactNode }) {
|
|
40
|
+
return (
|
|
41
|
+
<span
|
|
42
|
+
role="tooltip"
|
|
43
|
+
className="pointer-events-none absolute left-full top-1/2 ml-1.5 z-50 whitespace-nowrap rounded-md border border-white/10 bg-[#2a2d33] px-2.5 py-1.5 text-[11px] font-medium text-white shadow-lg opacity-0 transition-[opacity,margin] duration-150 ease-out group-hover:opacity-100 group-hover:ml-3"
|
|
44
|
+
style={{ transform: "translateY(-50%)" }}
|
|
45
|
+
>
|
|
46
|
+
{children}
|
|
47
|
+
</span>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ============================================
|
|
52
|
+
// Andami Mark — inlined from docs/andami_mark.svg
|
|
53
|
+
// ============================================
|
|
54
|
+
|
|
55
|
+
function AndamiMark({ size = 30 }: { size?: number }) {
|
|
56
|
+
// viewBox cropped to the actual content bbox (the original 0 0 500 500
|
|
57
|
+
// had ~36% empty padding that made the mark render tiny). Centered on the
|
|
58
|
+
// shape's true centroid (249.75, 240.2) with a 280px padded square.
|
|
59
|
+
return (
|
|
60
|
+
<svg
|
|
61
|
+
width={size}
|
|
62
|
+
height={size}
|
|
63
|
+
viewBox="110 100 280 280"
|
|
64
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
65
|
+
aria-hidden
|
|
66
|
+
>
|
|
67
|
+
<path
|
|
68
|
+
fill="#3580f9"
|
|
69
|
+
d="M228.8,166.3h39.6c3.1,0,5.7-2.5,5.7-5.6V121c0-3.1-2.6-5.7-5.7-5.7h-39.6c-3.1,0-5.7,2.6-5.7,5.7v39.6 C223.1,163.8,225.7,166.3,228.8,166.3z"
|
|
70
|
+
/>
|
|
71
|
+
<path
|
|
72
|
+
fill="#1E2025"
|
|
73
|
+
d="M227.2,185.8h-39.6c-3.1,0-5.7,2.6-5.7,5.7v39.6c0,3.1,2.6,5.7,5.7,5.7h39.6c3.1,0,5.7-2.6,5.7-5.7v-39.6 C232.9,188.4,230.3,185.8,227.2,185.8z"
|
|
74
|
+
/>
|
|
75
|
+
<path
|
|
76
|
+
fill="#1E2025"
|
|
77
|
+
d="M311.4,231.1v-39.6c0-3.1-2.6-5.7-5.7-5.7h-39.6c-3.1,0-5.7,2.6-5.7,5.7v39.6c0,3.1,2.6,5.7,5.7,5.7h39.6 C308.8,236.8,311.4,234.3,311.4,231.1z"
|
|
78
|
+
/>
|
|
79
|
+
<path
|
|
80
|
+
fill="#1E2025"
|
|
81
|
+
d="M332.7,258.4h-41.3c-3.3,0-6,2.7-6,6v94.7c0,3.3,2.7,6,6,6h41.3c3.3,0,6-2.7,6-6v-94.7 C338.7,261.1,336,258.4,332.7,258.4z"
|
|
82
|
+
/>
|
|
83
|
+
<path
|
|
84
|
+
fill="#1E2025"
|
|
85
|
+
d="M208.1,258.4h-41.3c-3.3,0-6,2.7-6,6v94.7c0,3.3,2.7,6,6,6h41.3c3.3,0,6-2.7,6-6v-94.7 C214.2,261.1,211.5,258.4,208.1,258.4z"
|
|
86
|
+
/>
|
|
87
|
+
</svg>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ============================================
|
|
92
|
+
// Icon Component — Mockup A set (Storage kept from original)
|
|
93
|
+
// ============================================
|
|
94
|
+
|
|
95
|
+
function NavIcon({ icon }: { icon: string }) {
|
|
96
|
+
const size = 20;
|
|
97
|
+
switch (icon) {
|
|
98
|
+
case "file":
|
|
99
|
+
// Tabler file-code (outline). Bumped to 22px — the file silhouette
|
|
100
|
+
// has empty space around the `< >` symbol, so it reads smaller than
|
|
101
|
+
// the other icons at the default 20px. Outline keeps it consistent
|
|
102
|
+
// with the rest of the sidebar.
|
|
103
|
+
return (
|
|
104
|
+
<svg width={22} height={22} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
105
|
+
<path d="M14 3v4a1 1 0 0 0 1 1h4" />
|
|
106
|
+
<path d="M17 21h-10a2 2 0 0 1 -2 -2v-14a2 2 0 0 1 2 -2h7l5 5v11a2 2 0 0 1 -2 2z" />
|
|
107
|
+
<path d="M10 13l-1 2l1 2" />
|
|
108
|
+
<path d="M14 13l1 2l-1 2" />
|
|
109
|
+
</svg>
|
|
110
|
+
);
|
|
111
|
+
case "film":
|
|
112
|
+
// Tray / archive — stack of items going into a container.
|
|
113
|
+
return (
|
|
114
|
+
<svg width={size} height={size} viewBox="0 0 24 24" fill="none">
|
|
115
|
+
<path d="M6.75 3.75H17.25" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
|
116
|
+
<path d="M4.75 7.25H19.25" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
|
117
|
+
<path d="M19.1556 10.75H4.84441C3.53306 10.75 2.57653 11.9908 2.91026 13.259L4.16144 18.0135C4.50827 19.3314 5.69986 20.25 7.06267 20.25H16.9373C18.3001 20.25 19.4917 19.3314 19.8386 18.0135L21.0897 13.259C21.4235 11.9908 20.4669 10.75 19.1556 10.75Z" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
|
118
|
+
</svg>
|
|
119
|
+
);
|
|
120
|
+
case "palette":
|
|
121
|
+
// Tabler color-swatch — geometric, matches the outline style of
|
|
122
|
+
// the rest of the sidebar.
|
|
123
|
+
return (
|
|
124
|
+
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
125
|
+
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
|
126
|
+
<path d="M19 3h-4a2 2 0 0 0 -2 2v12a4 4 0 0 0 8 0v-12a2 2 0 0 0 -2 -2" />
|
|
127
|
+
<path d="M13 7.35l-2 -2a2 2 0 0 0 -2.828 0l-2.828 2.828a2 2 0 0 0 0 2.828l9 9" />
|
|
128
|
+
<path d="M7.3 13h-2.3a2 2 0 0 0 -2 2v4a2 2 0 0 0 2 2h12" />
|
|
129
|
+
<path d="M17 17l0 .01" />
|
|
130
|
+
</svg>
|
|
131
|
+
);
|
|
132
|
+
case "nav":
|
|
133
|
+
return (
|
|
134
|
+
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
135
|
+
<line x1="4" y1="7" x2="20" y2="7" />
|
|
136
|
+
<line x1="4" y1="12" x2="20" y2="12" />
|
|
137
|
+
<line x1="4" y1="17" x2="14" y2="17" />
|
|
138
|
+
</svg>
|
|
139
|
+
);
|
|
140
|
+
case "database":
|
|
141
|
+
return (
|
|
142
|
+
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
143
|
+
<ellipse cx="12" cy="6" rx="8" ry="3" />
|
|
144
|
+
<path d="M4 6v6c0 1.7 3.6 3 8 3s8-1.3 8-3V6" />
|
|
145
|
+
<path d="M4 12v6c0 1.7 3.6 3 8 3s8-1.3 8-3v-6" />
|
|
146
|
+
</svg>
|
|
147
|
+
);
|
|
148
|
+
case "harddisk":
|
|
149
|
+
return (
|
|
150
|
+
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
151
|
+
<path d="M22 12H2" />
|
|
152
|
+
<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" />
|
|
153
|
+
<line x1="6" y1="16" x2="6.01" y2="16" strokeWidth="2" />
|
|
154
|
+
<line x1="10" y1="16" x2="10.01" y2="16" strokeWidth="2" />
|
|
155
|
+
</svg>
|
|
156
|
+
);
|
|
157
|
+
case "code":
|
|
158
|
+
// Tag — reads as "title, description, OG image, SEO", which is
|
|
159
|
+
// what this page actually edits (the old chevrons looked like
|
|
160
|
+
// "developer tool").
|
|
161
|
+
return (
|
|
162
|
+
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
163
|
+
<path d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z" />
|
|
164
|
+
<circle cx="7" cy="7" r="1" fill="currentColor" stroke="none" />
|
|
165
|
+
</svg>
|
|
166
|
+
);
|
|
167
|
+
case "cloud-download":
|
|
168
|
+
return (
|
|
169
|
+
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
170
|
+
<path d="M20 16.5A4.5 4.5 0 0 0 17 8.5 7 7 0 0 0 4 9a5 5 0 0 0 1 9.9" />
|
|
171
|
+
<path d="M12 12v8" />
|
|
172
|
+
<path d="M8.5 16.5L12 20l3.5-3.5" />
|
|
173
|
+
</svg>
|
|
174
|
+
);
|
|
175
|
+
case "setup":
|
|
176
|
+
return (
|
|
177
|
+
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
178
|
+
<path d="M14.7 6.3a4 4 0 0 0-5.6 5.6l-6 6a1.5 1.5 0 0 0 2.1 2.1l6-6a4 4 0 0 0 5.6-5.6l-2.2 2.2-2.1-2.1z" />
|
|
179
|
+
</svg>
|
|
180
|
+
);
|
|
181
|
+
case "view":
|
|
182
|
+
return (
|
|
183
|
+
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
184
|
+
<path d="M15 3h6v6" />
|
|
185
|
+
<path d="M10 14L21 3" />
|
|
186
|
+
<path d="M21 14v5a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5" />
|
|
187
|
+
</svg>
|
|
188
|
+
);
|
|
189
|
+
case "logout":
|
|
190
|
+
return (
|
|
191
|
+
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
192
|
+
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" />
|
|
193
|
+
<polyline points="16 17 21 12 16 7" />
|
|
194
|
+
<line x1="21" y1="12" x2="9" y2="12" />
|
|
195
|
+
</svg>
|
|
196
|
+
);
|
|
197
|
+
default:
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ============================================
|
|
203
|
+
// Layout Component — collapsed-only sidebar
|
|
204
|
+
// ============================================
|
|
205
|
+
|
|
206
|
+
export default function AdminLayout({
|
|
207
|
+
children,
|
|
208
|
+
}: {
|
|
209
|
+
children: React.ReactNode;
|
|
210
|
+
}) {
|
|
211
|
+
const pathname = usePathname();
|
|
212
|
+
const router = useRouter();
|
|
213
|
+
|
|
214
|
+
const isPageBuilder =
|
|
215
|
+
/^\/admin\/pages\/[^/]+$/.test(pathname) ||
|
|
216
|
+
/^\/admin\/projects\/[^/]+$/.test(pathname);
|
|
217
|
+
|
|
218
|
+
// Don't show admin shell on login or setup pages
|
|
219
|
+
if (pathname === "/admin/login" || pathname === "/admin/setup") {
|
|
220
|
+
return <>{children}</>;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const handleLogout = async () => {
|
|
224
|
+
await fetch("/api/admin/auth", { method: "DELETE" });
|
|
225
|
+
router.push("/admin/login");
|
|
226
|
+
router.refresh();
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const isLinkActive = (href: string) =>
|
|
230
|
+
pathname === href || pathname.startsWith(href + "/");
|
|
231
|
+
|
|
232
|
+
const tileBase = `group relative flex items-center justify-center ${TILE_SIZE} ${TILE_RADIUS} transition-colors`;
|
|
233
|
+
|
|
234
|
+
const renderNavLink = (link: { href: string; label: string; icon: string }) => {
|
|
235
|
+
const isActive = isLinkActive(link.href);
|
|
236
|
+
return (
|
|
237
|
+
<Link
|
|
238
|
+
key={link.href}
|
|
239
|
+
href={link.href}
|
|
240
|
+
className={`${tileBase} ${
|
|
241
|
+
isActive
|
|
242
|
+
? "bg-[#3580f9] text-white"
|
|
243
|
+
: "text-white/70 hover:bg-white/[0.06] hover:text-white"
|
|
244
|
+
}`}
|
|
245
|
+
aria-label={link.label}
|
|
246
|
+
>
|
|
247
|
+
<NavIcon icon={link.icon} />
|
|
248
|
+
<Tooltip>{link.label}</Tooltip>
|
|
249
|
+
</Link>
|
|
250
|
+
);
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
return (
|
|
254
|
+
<div
|
|
255
|
+
data-admin
|
|
256
|
+
className="flex h-screen bg-[#f8f8f8]"
|
|
257
|
+
style={{ fontFamily: "Inter, system-ui, sans-serif" }}
|
|
258
|
+
>
|
|
259
|
+
{/* Sidebar — collapsed-only (64px) */}
|
|
260
|
+
<aside className="flex w-16 flex-col items-center bg-gradient-to-b from-[#1e2025] to-[#1a1c20] border-r border-white/[0.07]">
|
|
261
|
+
{/* Logo — white tile with the Andami mark, same square as nav items */}
|
|
262
|
+
<div className="pt-3 pb-2">
|
|
263
|
+
<div
|
|
264
|
+
className={`${tileBase} bg-white`}
|
|
265
|
+
aria-label={`Morphika Andami v${ANDAMI_VERSION}`}
|
|
266
|
+
>
|
|
267
|
+
<AndamiMark size={30} />
|
|
268
|
+
<Tooltip>
|
|
269
|
+
Morphika Andami
|
|
270
|
+
<span className="ml-1.5 text-white/55">v{ANDAMI_VERSION}</span>
|
|
271
|
+
</Tooltip>
|
|
272
|
+
</div>
|
|
273
|
+
</div>
|
|
274
|
+
|
|
275
|
+
{/* Workspace nav */}
|
|
276
|
+
<nav className="flex flex-1 flex-col items-center gap-1.5 pt-2 pb-2">
|
|
277
|
+
{workspaceLinks.map(renderNavLink)}
|
|
278
|
+
|
|
279
|
+
<div className="my-2 h-px w-6 bg-white/10" aria-hidden />
|
|
280
|
+
|
|
281
|
+
{systemLinks.map(renderNavLink)}
|
|
282
|
+
</nav>
|
|
283
|
+
|
|
284
|
+
{/* Utility footer */}
|
|
285
|
+
<div className="flex flex-col items-center gap-1.5 pb-3">
|
|
286
|
+
<div className="mb-2 h-px w-6 bg-white/10" aria-hidden />
|
|
287
|
+
|
|
288
|
+
<Link
|
|
289
|
+
href="/admin/setup"
|
|
290
|
+
className={`${tileBase} text-white/55 hover:bg-white/[0.06] hover:text-white`}
|
|
291
|
+
aria-label="Setup Wizard"
|
|
292
|
+
>
|
|
293
|
+
<NavIcon icon="setup" />
|
|
294
|
+
<Tooltip>Setup Wizard</Tooltip>
|
|
295
|
+
</Link>
|
|
296
|
+
|
|
297
|
+
<Link
|
|
298
|
+
href="/"
|
|
299
|
+
target="_blank"
|
|
300
|
+
className={`${tileBase} text-white/55 hover:bg-white/[0.06] hover:text-white`}
|
|
301
|
+
aria-label="View Site"
|
|
302
|
+
>
|
|
303
|
+
<NavIcon icon="view" />
|
|
304
|
+
<Tooltip>View Site</Tooltip>
|
|
305
|
+
</Link>
|
|
306
|
+
|
|
307
|
+
<button
|
|
308
|
+
onClick={handleLogout}
|
|
309
|
+
className={`${tileBase} text-white/55 hover:bg-red-500/[0.1] hover:text-red-300`}
|
|
310
|
+
aria-label="Log out"
|
|
311
|
+
>
|
|
312
|
+
<NavIcon icon="logout" />
|
|
313
|
+
<Tooltip>Log out</Tooltip>
|
|
314
|
+
</button>
|
|
315
|
+
</div>
|
|
316
|
+
</aside>
|
|
317
|
+
|
|
318
|
+
{/* Main content area — no top header bar, pages have their own titles */}
|
|
319
|
+
<div className="flex flex-1 flex-col overflow-hidden">
|
|
320
|
+
<main
|
|
321
|
+
className={`flex-1 ${
|
|
322
|
+
isPageBuilder
|
|
323
|
+
? "overflow-hidden"
|
|
324
|
+
: "overflow-y-auto p-8 bg-[#f8f8f8]"
|
|
325
|
+
}`}
|
|
326
|
+
>
|
|
327
|
+
{children}
|
|
328
|
+
</main>
|
|
329
|
+
</div>
|
|
330
|
+
</div>
|
|
331
|
+
);
|
|
332
|
+
}
|