@morphika/andami 0.5.0 → 0.5.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.
- package/README.md +151 -36
- 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 +320 -327
- package/app/admin/navigation/page.tsx +255 -255
- package/app/admin/pages/[slug]/page.tsx +6 -6
- package/app/admin/pages/page.tsx +11 -11
- package/app/admin/projects/page.tsx +14 -14
- package/app/admin/setup/page.tsx +1 -1
- package/app/admin/styles/page.tsx +1 -1
- package/components/admin/MetadataEditor.tsx +6 -6
- 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 +4 -4
- 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 +514 -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 +2 -2
- package/components/admin/styles/FontsEditor.tsx +6 -6
- 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 -0
- package/components/blocks/BeforeAfterBlockRenderer.tsx +274 -0
- package/components/blocks/MarqueeBlockRenderer.tsx +316 -0
- package/components/blocks/ProjectCarouselBlockRenderer.tsx +1 -1
- package/components/builder/BlockCardIcons.tsx +316 -227
- package/components/builder/BlockTypePicker.tsx +3 -1
- package/components/builder/BubbleIcons.tsx +90 -0
- package/components/builder/BuilderCanvas.tsx +2 -0
- package/components/builder/CanvasMinimap.tsx +2 -2
- package/components/builder/CoverSectionCanvas.tsx +363 -275
- 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 +1 -1
- package/components/builder/SectionTypePicker.tsx +4 -4
- package/components/builder/SectionV2Canvas.tsx +20 -4
- package/components/builder/SectionV2Column.tsx +74 -68
- package/components/builder/SortableBlock.tsx +93 -73
- package/components/builder/SortableRow.tsx +27 -26
- package/components/builder/VirtualAssetGrid.tsx +2 -2
- package/components/builder/asset-browser/R2BrowserContent.tsx +34 -17
- package/components/builder/asset-browser/helpers.ts +4 -0
- package/components/builder/asset-browser/types.ts +2 -1
- package/components/builder/blockStyles.tsx +192 -173
- 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 +74 -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 +93 -93
- package/components/builder/editors/AudioBlockEditor.tsx +242 -0
- package/components/builder/editors/BeforeAfterBlockEditor.tsx +360 -0
- 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 +4 -4
- package/components/builder/editors/MarqueeBlockEditor.tsx +621 -0
- package/components/builder/editors/ProjectCarouselBlockEditor.tsx +443 -443
- package/components/builder/editors/ProjectGridEditor.tsx +9 -9
- package/components/builder/editors/SpacerBlockEditor.tsx +5 -5
- package/components/builder/editors/StaggerSettings.tsx +109 -109
- package/components/builder/editors/TextBlockEditor.tsx +3 -3
- 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 +7 -7
- package/components/builder/live-preview/LiveAudioPreview.tsx +120 -0
- package/components/builder/live-preview/LiveBeforeAfterPreview.tsx +176 -0
- package/components/builder/live-preview/LiveImageGridPreview.tsx +10 -2
- package/components/builder/live-preview/LiveImagePreview.tsx +1 -1
- 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 +291 -291
- package/components/builder/settings-panel/AnimationTab.tsx +138 -138
- package/components/builder/settings-panel/BlockLayoutTab.tsx +7 -7
- package/components/builder/settings-panel/CardEntranceSection.tsx +114 -114
- 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 +335 -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 +14 -14
- package/components/builder/settings-panel/TRBLInputs.tsx +1 -1
- package/lib/animation/enter-types.ts +3 -0
- package/lib/animation/hover-effect-presets.ts +210 -210
- package/lib/animation/hover-effect-types.ts +3 -0
- package/lib/builder/block-registrations.ts +468 -335
- package/lib/builder/constants.ts +111 -111
- package/lib/builder/store-sections.ts +2 -2
- package/lib/builder/types-slices.ts +414 -414
- package/lib/builder/types.ts +6 -1
- package/lib/config/index.ts +27 -27
- package/lib/sanity/types.ts +156 -1
- package/lib/version.ts +1 -1
- package/package.json +1 -1
- package/sanity/schemas/blocks/audioBlock.ts +69 -0
- package/sanity/schemas/blocks/beforeAfterBlock.ts +121 -0
- package/sanity/schemas/blocks/index.ts +12 -9
- package/sanity/schemas/blocks/marqueeBlock.ts +292 -0
- package/sanity/schemas/index.ts +120 -111
- 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,327 +1,320 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { usePathname, useRouter } from "next/navigation";
|
|
4
|
-
import Link from "next/link";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
{ href: "/admin/
|
|
15
|
-
{ href: "/admin/
|
|
16
|
-
{ href: "/admin/
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
{ href: "/admin/
|
|
22
|
-
{ href: "/admin/
|
|
23
|
-
{ href: "/admin/
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
//
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
-
|
|
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
|
-
<Link
|
|
286
|
-
href="/"
|
|
287
|
-
target="_blank"
|
|
288
|
-
className={`${
|
|
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
|
-
|
|
321
|
-
}`}>
|
|
322
|
-
{children}
|
|
323
|
-
</main>
|
|
324
|
-
</div>
|
|
325
|
-
</div>
|
|
326
|
-
);
|
|
327
|
-
}
|
|
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
|
+
return (
|
|
100
|
+
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
101
|
+
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
|
|
102
|
+
<path d="M14 2v6h6" />
|
|
103
|
+
</svg>
|
|
104
|
+
);
|
|
105
|
+
case "film":
|
|
106
|
+
return (
|
|
107
|
+
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
108
|
+
<rect x="3" y="3" width="7" height="7" rx="1.5" />
|
|
109
|
+
<rect x="14" y="3" width="7" height="7" rx="1.5" />
|
|
110
|
+
<rect x="3" y="14" width="7" height="7" rx="1.5" />
|
|
111
|
+
<rect x="14" y="14" width="7" height="7" rx="1.5" />
|
|
112
|
+
</svg>
|
|
113
|
+
);
|
|
114
|
+
case "palette":
|
|
115
|
+
return (
|
|
116
|
+
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
117
|
+
<circle cx="12" cy="12" r="9" />
|
|
118
|
+
<circle cx="8" cy="10" r="1" />
|
|
119
|
+
<circle cx="16" cy="10" r="1" />
|
|
120
|
+
<circle cx="12" cy="15" r="1" />
|
|
121
|
+
</svg>
|
|
122
|
+
);
|
|
123
|
+
case "nav":
|
|
124
|
+
return (
|
|
125
|
+
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
126
|
+
<line x1="4" y1="7" x2="20" y2="7" />
|
|
127
|
+
<line x1="4" y1="12" x2="20" y2="12" />
|
|
128
|
+
<line x1="4" y1="17" x2="14" y2="17" />
|
|
129
|
+
</svg>
|
|
130
|
+
);
|
|
131
|
+
case "database":
|
|
132
|
+
return (
|
|
133
|
+
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
134
|
+
<ellipse cx="12" cy="6" rx="8" ry="3" />
|
|
135
|
+
<path d="M4 6v6c0 1.7 3.6 3 8 3s8-1.3 8-3V6" />
|
|
136
|
+
<path d="M4 12v6c0 1.7 3.6 3 8 3s8-1.3 8-3v-6" />
|
|
137
|
+
</svg>
|
|
138
|
+
);
|
|
139
|
+
case "harddisk":
|
|
140
|
+
return (
|
|
141
|
+
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
142
|
+
<path d="M22 12H2" />
|
|
143
|
+
<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" />
|
|
144
|
+
<line x1="6" y1="16" x2="6.01" y2="16" strokeWidth="2" />
|
|
145
|
+
<line x1="10" y1="16" x2="10.01" y2="16" strokeWidth="2" />
|
|
146
|
+
</svg>
|
|
147
|
+
);
|
|
148
|
+
case "code":
|
|
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
|
+
<polyline points="8 6 3 12 8 18" />
|
|
152
|
+
<polyline points="16 6 21 12 16 18" />
|
|
153
|
+
</svg>
|
|
154
|
+
);
|
|
155
|
+
case "cloud-download":
|
|
156
|
+
return (
|
|
157
|
+
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
158
|
+
<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" />
|
|
159
|
+
<path d="M12 12v8" />
|
|
160
|
+
<path d="M8.5 16.5L12 20l3.5-3.5" />
|
|
161
|
+
</svg>
|
|
162
|
+
);
|
|
163
|
+
case "setup":
|
|
164
|
+
return (
|
|
165
|
+
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
166
|
+
<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" />
|
|
167
|
+
</svg>
|
|
168
|
+
);
|
|
169
|
+
case "view":
|
|
170
|
+
return (
|
|
171
|
+
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
172
|
+
<path d="M15 3h6v6" />
|
|
173
|
+
<path d="M10 14L21 3" />
|
|
174
|
+
<path d="M21 14v5a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5" />
|
|
175
|
+
</svg>
|
|
176
|
+
);
|
|
177
|
+
case "logout":
|
|
178
|
+
return (
|
|
179
|
+
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
180
|
+
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" />
|
|
181
|
+
<polyline points="16 17 21 12 16 7" />
|
|
182
|
+
<line x1="21" y1="12" x2="9" y2="12" />
|
|
183
|
+
</svg>
|
|
184
|
+
);
|
|
185
|
+
default:
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ============================================
|
|
191
|
+
// Layout Component — collapsed-only sidebar
|
|
192
|
+
// ============================================
|
|
193
|
+
|
|
194
|
+
export default function AdminLayout({
|
|
195
|
+
children,
|
|
196
|
+
}: {
|
|
197
|
+
children: React.ReactNode;
|
|
198
|
+
}) {
|
|
199
|
+
const pathname = usePathname();
|
|
200
|
+
const router = useRouter();
|
|
201
|
+
|
|
202
|
+
const isPageBuilder =
|
|
203
|
+
/^\/admin\/pages\/[^/]+$/.test(pathname) ||
|
|
204
|
+
/^\/admin\/projects\/[^/]+$/.test(pathname);
|
|
205
|
+
|
|
206
|
+
// Don't show admin shell on login or setup pages
|
|
207
|
+
if (pathname === "/admin/login" || pathname === "/admin/setup") {
|
|
208
|
+
return <>{children}</>;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const handleLogout = async () => {
|
|
212
|
+
await fetch("/api/admin/auth", { method: "DELETE" });
|
|
213
|
+
router.push("/admin/login");
|
|
214
|
+
router.refresh();
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const isLinkActive = (href: string) =>
|
|
218
|
+
pathname === href || pathname.startsWith(href + "/");
|
|
219
|
+
|
|
220
|
+
const tileBase = `group relative flex items-center justify-center ${TILE_SIZE} ${TILE_RADIUS} transition-colors`;
|
|
221
|
+
|
|
222
|
+
const renderNavLink = (link: { href: string; label: string; icon: string }) => {
|
|
223
|
+
const isActive = isLinkActive(link.href);
|
|
224
|
+
return (
|
|
225
|
+
<Link
|
|
226
|
+
key={link.href}
|
|
227
|
+
href={link.href}
|
|
228
|
+
className={`${tileBase} ${
|
|
229
|
+
isActive
|
|
230
|
+
? "bg-[#3580f9] text-white"
|
|
231
|
+
: "text-white/70 hover:bg-white/[0.06] hover:text-white"
|
|
232
|
+
}`}
|
|
233
|
+
aria-label={link.label}
|
|
234
|
+
>
|
|
235
|
+
<NavIcon icon={link.icon} />
|
|
236
|
+
<Tooltip>{link.label}</Tooltip>
|
|
237
|
+
</Link>
|
|
238
|
+
);
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
return (
|
|
242
|
+
<div
|
|
243
|
+
data-admin
|
|
244
|
+
className="flex h-screen bg-[#f8f8f8]"
|
|
245
|
+
style={{ fontFamily: "Inter, system-ui, sans-serif" }}
|
|
246
|
+
>
|
|
247
|
+
{/* Sidebar — collapsed-only (64px) */}
|
|
248
|
+
<aside className="flex w-16 flex-col items-center bg-gradient-to-b from-[#1e2025] to-[#1a1c20] border-r border-white/[0.07]">
|
|
249
|
+
{/* Logo — white tile with the Andami mark, same square as nav items */}
|
|
250
|
+
<div className="pt-3 pb-2">
|
|
251
|
+
<div
|
|
252
|
+
className={`${tileBase} bg-white`}
|
|
253
|
+
aria-label={`Morphika Andami v${ANDAMI_VERSION}`}
|
|
254
|
+
>
|
|
255
|
+
<AndamiMark size={30} />
|
|
256
|
+
<Tooltip>
|
|
257
|
+
Morphika Andami
|
|
258
|
+
<span className="ml-1.5 text-white/55">v{ANDAMI_VERSION}</span>
|
|
259
|
+
</Tooltip>
|
|
260
|
+
</div>
|
|
261
|
+
</div>
|
|
262
|
+
|
|
263
|
+
{/* Workspace nav */}
|
|
264
|
+
<nav className="flex flex-1 flex-col items-center gap-1.5 pt-2 pb-2">
|
|
265
|
+
{workspaceLinks.map(renderNavLink)}
|
|
266
|
+
|
|
267
|
+
<div className="my-2 h-px w-6 bg-white/10" aria-hidden />
|
|
268
|
+
|
|
269
|
+
{systemLinks.map(renderNavLink)}
|
|
270
|
+
</nav>
|
|
271
|
+
|
|
272
|
+
{/* Utility footer */}
|
|
273
|
+
<div className="flex flex-col items-center gap-1.5 pb-3">
|
|
274
|
+
<div className="mb-2 h-px w-6 bg-white/10" aria-hidden />
|
|
275
|
+
|
|
276
|
+
<Link
|
|
277
|
+
href="/admin/setup"
|
|
278
|
+
className={`${tileBase} text-white/55 hover:bg-white/[0.06] hover:text-white`}
|
|
279
|
+
aria-label="Setup Wizard"
|
|
280
|
+
>
|
|
281
|
+
<NavIcon icon="setup" />
|
|
282
|
+
<Tooltip>Setup Wizard</Tooltip>
|
|
283
|
+
</Link>
|
|
284
|
+
|
|
285
|
+
<Link
|
|
286
|
+
href="/"
|
|
287
|
+
target="_blank"
|
|
288
|
+
className={`${tileBase} text-white/55 hover:bg-white/[0.06] hover:text-white`}
|
|
289
|
+
aria-label="View Site"
|
|
290
|
+
>
|
|
291
|
+
<NavIcon icon="view" />
|
|
292
|
+
<Tooltip>View Site</Tooltip>
|
|
293
|
+
</Link>
|
|
294
|
+
|
|
295
|
+
<button
|
|
296
|
+
onClick={handleLogout}
|
|
297
|
+
className={`${tileBase} text-white/55 hover:bg-red-500/[0.1] hover:text-red-300`}
|
|
298
|
+
aria-label="Log out"
|
|
299
|
+
>
|
|
300
|
+
<NavIcon icon="logout" />
|
|
301
|
+
<Tooltip>Log out</Tooltip>
|
|
302
|
+
</button>
|
|
303
|
+
</div>
|
|
304
|
+
</aside>
|
|
305
|
+
|
|
306
|
+
{/* Main content area — no top header bar, pages have their own titles */}
|
|
307
|
+
<div className="flex flex-1 flex-col overflow-hidden">
|
|
308
|
+
<main
|
|
309
|
+
className={`flex-1 ${
|
|
310
|
+
isPageBuilder
|
|
311
|
+
? "overflow-hidden"
|
|
312
|
+
: "overflow-y-auto p-8 bg-[#f8f8f8]"
|
|
313
|
+
}`}
|
|
314
|
+
>
|
|
315
|
+
{children}
|
|
316
|
+
</main>
|
|
317
|
+
</div>
|
|
318
|
+
</div>
|
|
319
|
+
);
|
|
320
|
+
}
|