@morphika/andami 0.5.1 → 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/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 -320
- 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 -286
- package/components/blocks/MarqueeBlockRenderer.tsx +316 -0
- package/components/blocks/ProjectCarouselBlockRenderer.tsx +1 -1
- package/components/builder/BlockCardIcons.tsx +316 -316
- package/components/builder/BlockTypePicker.tsx +1 -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 -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 +1 -1
- package/components/builder/SectionTypePicker.tsx +4 -4
- package/components/builder/SectionV2Canvas.tsx +1 -1
- package/components/builder/SectionV2Column.tsx +69 -67
- 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 +11 -11
- 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 +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 -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 +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 +6 -6
- 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 +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 +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/store-sections.ts +2 -2
- package/lib/builder/types-slices.ts +414 -414
- package/lib/builder/types.ts +4 -1
- package/lib/config/index.ts +27 -27
- package/lib/sanity/types.ts +98 -1
- package/lib/version.ts +1 -1
- package/package.json +1 -1
- 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/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,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="#
|
|
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-[#
|
|
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
|
-
}
|
|
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
|
+
}
|