@sprintup-cms/sdk 1.8.57 → 1.8.62
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 +58 -37
- package/dist/next/index.cjs +139 -85
- package/dist/next/index.cjs.map +1 -1
- package/dist/next/index.js +139 -85
- package/dist/next/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ Official SDK for **SprintUp Forge CMS** — typed API client, Next.js App Router
|
|
|
17
17
|
4. [Verify your connection](#verify-your-connection)
|
|
18
18
|
5. [Adding new pages](#adding-new-pages)
|
|
19
19
|
6. [Custom page layouts](#custom-page-layouts)
|
|
20
|
-
7. [Navigation and footer](#navigation-and-footer)
|
|
20
|
+
7. [Navigation and footer](#navigation-and-footer) — automatically rendered by the SDK via CMS Globals
|
|
21
21
|
8. [Extending with custom blocks](#extending-with-custom-blocks)
|
|
22
22
|
9. [ISR and caching strategy](#isr-and-caching-strategy)
|
|
23
23
|
10. [API reference](#api-reference)
|
|
@@ -288,48 +288,60 @@ export default async function Page({ params }: { params: Promise<{ slug: string[
|
|
|
288
288
|
|
|
289
289
|
## Navigation and footer
|
|
290
290
|
|
|
291
|
-
|
|
291
|
+
> **As of v1.8.62**, navigation and footer are managed as **CMS Globals** — configured entirely in the CMS Admin under **Globals → Navigation** and **Globals → Footer**. You do not need to write any header or footer code. The SDK renders them automatically.
|
|
292
292
|
|
|
293
|
-
|
|
294
|
-
// components/layout/header.tsx
|
|
295
|
-
import { cmsClient } from '@sprintup-cms/sdk'
|
|
296
|
-
import type { CMSMenuItem } from '@sprintup-cms/sdk'
|
|
297
|
-
import Link from 'next/link'
|
|
293
|
+
### How it works
|
|
298
294
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
295
|
+
`CMSCatchAllPage` (the SDK's catch-all page handler) automatically fetches the active globals for your app and renders:
|
|
296
|
+
- `<CMSHeader>` — a sticky top nav bar with your logo, nav links, and CTA buttons
|
|
297
|
+
- `<CMSFooter>` — a configurable section grid with optional footer bottom bar
|
|
302
298
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
299
|
+
**You do not need to create `components/layout/header.tsx` or `components/layout/footer.tsx`.**
|
|
300
|
+
|
|
301
|
+
### Configuring navigation in the CMS
|
|
302
|
+
|
|
303
|
+
Go to **CMS Admin → Globals → Navigation** and add items:
|
|
304
|
+
|
|
305
|
+
| Item type | Rendered as | Options |
|
|
306
|
+
|---|---|---|
|
|
307
|
+
| `link` | Nav link in the centre area | Label, URL, open in new tab |
|
|
308
|
+
| `button` | CTA button, right-aligned | Label, URL, variant: `primary` / `outline` / `ghost` |
|
|
309
|
+
| `dropdown` | Link with children | Label, URL, child links |
|
|
310
|
+
|
|
311
|
+
### Configuring footer in the CMS
|
|
312
|
+
|
|
313
|
+
Go to **CMS Admin → Globals → Footer** and add sections in any order:
|
|
314
|
+
|
|
315
|
+
| Section type | Description |
|
|
316
|
+
|---|---|
|
|
317
|
+
| `brand` | Logo image and tagline |
|
|
318
|
+
| `links` | A column of links with a custom title — add as many as you need |
|
|
319
|
+
| `contact` | Email, phone, address with a custom section title |
|
|
320
|
+
| `social` | Facebook, X, Instagram, LinkedIn, YouTube icons |
|
|
321
|
+
|
|
322
|
+
Optionally enable **Footer Bottom** (a full-width legal bar):
|
|
323
|
+
- Copyright text
|
|
324
|
+
- Legal links (Privacy Policy, Terms, etc.)
|
|
325
|
+
|
|
326
|
+
### Links: internal vs external
|
|
327
|
+
|
|
328
|
+
When adding links inside footer sections, the link input auto-searches your published CMS pages as you type. Select a page to create an internal link, or type a full URL (`https://...`) for an external link. External links open in a new tab automatically.
|
|
329
|
+
|
|
330
|
+
### Custom layout: accessing globals manually
|
|
331
|
+
|
|
332
|
+
If you are building a custom page outside `CMSCatchAllPage`, you can access globals directly:
|
|
316
333
|
|
|
317
334
|
```tsx
|
|
318
|
-
//
|
|
319
|
-
import {
|
|
335
|
+
// Only needed for custom layouts — CMSCatchAllPage handles this automatically
|
|
336
|
+
import { cmsClient } from '@sprintup-cms/sdk'
|
|
320
337
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
<body>
|
|
325
|
-
<Header />
|
|
326
|
-
{children}
|
|
327
|
-
</body>
|
|
328
|
-
</html>
|
|
329
|
-
)
|
|
330
|
-
}
|
|
338
|
+
const globals = await cmsClient.getGlobals()
|
|
339
|
+
const nav = globals?.nav // CMSPage with sectionData.items[]
|
|
340
|
+
const footer = globals?.footer // CMSPage with sectionData.sections[]
|
|
331
341
|
```
|
|
332
342
|
|
|
343
|
+
> **Deprecated:** `cmsClient.getSiteStructure()` and `menus.header` / `menus.footer` are the old approach and should not be used for navigation or footer.
|
|
344
|
+
|
|
333
345
|
---
|
|
334
346
|
|
|
335
347
|
## Extending with custom blocks
|
|
@@ -370,9 +382,10 @@ The `custom` prop is a `Record<blockType, (block: CMSBlock) => React.ReactNode>`
|
|
|
370
382
|
| Individual page | 60 seconds | `cms-page-{slug}` |
|
|
371
383
|
| All pages list | 60 seconds | `cms-pages-{appId}` |
|
|
372
384
|
| Page type schema | 3600 seconds | `cms-page-type-{id}` |
|
|
373
|
-
|
|
|
385
|
+
| Globals (nav + footer) | 60 seconds | `cms-globals-{appId}` |
|
|
374
386
|
| Sitemap | 3600 seconds | — |
|
|
375
387
|
| Status check | No cache | — |
|
|
388
|
+
| ~~Site structure~~ | ~~300 seconds~~ | Deprecated — use Globals |
|
|
376
389
|
|
|
377
390
|
The revalidation webhook calls `revalidateTag('cms-page-{slug}')` and `revalidatePath('/{slug}')` when the CMS publishes a page, giving you **instant updates** without a full rebuild.
|
|
378
391
|
|
|
@@ -390,11 +403,12 @@ The revalidation webhook calls `revalidateTag('cms-page-{slug}')` and `revalidat
|
|
|
390
403
|
| `getEvents()` | Shorthand for `getPages({ type: 'event-page' })` |
|
|
391
404
|
| `getAnnouncements()` | Shorthand for `getPages({ type: 'announcement-page' })` |
|
|
392
405
|
| `getPageType(id)` | Fetch a page type schema by ID |
|
|
393
|
-
| `
|
|
406
|
+
| `getGlobals()` | Fetch navigation and footer globals — returns `{ nav, footer }`. CMSCatchAllPage calls this automatically. |
|
|
394
407
|
| `getPreviewPage(token)` | Fetch a draft page for preview mode |
|
|
395
408
|
| `getPageWithPreview(slug, token?)` | Fetch page — preview if token present, live otherwise |
|
|
396
409
|
| `getSitemap()` | Fetch all published slugs with sitemap metadata |
|
|
397
410
|
| `getStatus()` | Connectivity check — returns counts and page list |
|
|
411
|
+
| ~~`getSiteStructure()`~~ | **Deprecated.** Used the old Site Structure editor. Navigation and footer are now CMS Globals — use `getGlobals()` or rely on CMSCatchAllPage. |
|
|
398
412
|
|
|
399
413
|
---
|
|
400
414
|
|
|
@@ -439,6 +453,13 @@ The revalidation webhook calls `revalidateTag('cms-page-{slug}')` and `revalidat
|
|
|
439
453
|
- `CMS_WEBHOOK_SECRET` on both sides must match exactly.
|
|
440
454
|
- Check your deployment logs for `[sprintup-cms] revalidate error:` messages.
|
|
441
455
|
|
|
456
|
+
**Navigation or footer not showing**
|
|
457
|
+
|
|
458
|
+
- Go to **CMS Admin → Globals → Navigation** (or Footer) and check that content is published (status = Published).
|
|
459
|
+
- Ensure at least one item/section is added. An empty globals document renders nothing.
|
|
460
|
+
- Changes are cached for 60 seconds. Hard-refresh or wait a moment after publishing.
|
|
461
|
+
- Do not use `getSiteStructure()` — it does not return globals data.
|
|
462
|
+
|
|
442
463
|
**Preview mode not working**
|
|
443
464
|
|
|
444
465
|
- The `/api/cms-preview/exit` route must exist.
|
package/dist/next/index.cjs
CHANGED
|
@@ -1151,100 +1151,154 @@ function ServerProductListBlock({ block }) {
|
|
|
1151
1151
|
}) })
|
|
1152
1152
|
] }) });
|
|
1153
1153
|
}
|
|
1154
|
-
function
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
return text.split(/\r?\n/).filter(Boolean).map((line) => {
|
|
1158
|
-
const [label, url] = line.split("|").map((s) => s.trim());
|
|
1159
|
-
return { label: label || "", url: url || "#" };
|
|
1160
|
-
});
|
|
1154
|
+
function getSD(doc) {
|
|
1155
|
+
const structuredBlock = Array.isArray(doc?.blocks) && doc.blocks[0]?.type === "__structured__" ? doc.blocks[0].content : null;
|
|
1156
|
+
return doc?.sectionData ?? structuredBlock ?? null;
|
|
1161
1157
|
}
|
|
1162
1158
|
function CMSHeader({ nav }) {
|
|
1163
|
-
|
|
1164
|
-
const structuredBlock = Array.isArray(nav.blocks) && nav.blocks[0]?.type === "__structured__" ? nav.blocks[0].content : null;
|
|
1165
|
-
const sd = nav.sectionData ?? structuredBlock;
|
|
1159
|
+
const sd = getSD(nav);
|
|
1166
1160
|
if (!sd) return null;
|
|
1167
|
-
const
|
|
1168
|
-
const
|
|
1169
|
-
const
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1161
|
+
const logoUrl = sd.logo_url?.trim() || "";
|
|
1162
|
+
const logoAlt = sd.logo_alt?.trim() || "";
|
|
1163
|
+
const items = Array.isArray(sd.items) ? sd.items : [];
|
|
1164
|
+
if (!logoUrl && !logoAlt && items.length === 0) return null;
|
|
1165
|
+
const links = items.filter((i) => i.type === "link" || i.type === "dropdown");
|
|
1166
|
+
const buttons = items.filter((i) => i.type === "button");
|
|
1167
|
+
const btnBase = { textDecoration: "none", padding: "0.4rem 1rem", borderRadius: "6px", fontWeight: 600, fontSize: "0.875rem", whiteSpace: "nowrap", transition: "opacity 0.15s" };
|
|
1168
|
+
const btnVariant = (v) => ({
|
|
1169
|
+
primary: { ...btnBase, background: "var(--foreground, #111)", color: "#fff", border: "1px solid transparent" },
|
|
1170
|
+
outline: { ...btnBase, background: "transparent", color: "var(--foreground, #111)", border: "1px solid var(--border, #e5e7eb)" },
|
|
1171
|
+
ghost: { ...btnBase, background: "transparent", color: "var(--muted-foreground, #6b7280)", border: "none", fontWeight: 500 }
|
|
1172
|
+
})[v] ?? btnBase;
|
|
1173
|
+
return /* @__PURE__ */ jsxRuntime.jsx("header", { style: { position: "sticky", top: 0, zIndex: 50, width: "100%", borderBottom: "1px solid var(--border, #e5e7eb)", background: "rgba(255,255,255,0.97)", backdropFilter: "blur(8px)", WebkitBackdropFilter: "blur(8px)" }, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { maxWidth: "1200px", margin: "0 auto", padding: "0 1.5rem", height: "64px", display: "flex", alignItems: "center", justifyContent: "space-between", gap: "2rem" }, children: [
|
|
1174
|
+
/* @__PURE__ */ jsxRuntime.jsx("a", { href: "/", style: { display: "flex", alignItems: "center", gap: "0.5rem", textDecoration: "none", flexShrink: 0 }, children: logoUrl ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: logoUrl, alt: logoAlt || "Logo", style: { height: "32px", width: "auto", display: "block" } }) : /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontWeight: 700, fontSize: "1.125rem", color: "var(--foreground, #111)" }, children: logoAlt }) }),
|
|
1175
|
+
links.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("nav", { style: { display: "flex", alignItems: "center", gap: "1.75rem", flex: 1 }, children: links.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1176
|
+
"a",
|
|
1177
|
+
{
|
|
1178
|
+
href: item.url || "#",
|
|
1179
|
+
target: item.openInNewTab ? "_blank" : void 0,
|
|
1180
|
+
rel: item.openInNewTab ? "noopener noreferrer" : void 0,
|
|
1181
|
+
style: { fontSize: "0.9rem", color: "var(--muted-foreground, #6b7280)", textDecoration: "none", fontWeight: 500 },
|
|
1182
|
+
children: item.label
|
|
1183
|
+
},
|
|
1184
|
+
item.id
|
|
1185
|
+
)) }),
|
|
1186
|
+
buttons.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", alignItems: "center", gap: "0.625rem", flexShrink: 0 }, children: buttons.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1187
|
+
"a",
|
|
1188
|
+
{
|
|
1189
|
+
href: item.url || "#",
|
|
1190
|
+
target: item.openInNewTab ? "_blank" : void 0,
|
|
1191
|
+
rel: item.openInNewTab ? "noopener noreferrer" : void 0,
|
|
1192
|
+
style: btnVariant(item.variant || "ghost"),
|
|
1193
|
+
children: item.label
|
|
1194
|
+
},
|
|
1195
|
+
item.id
|
|
1196
|
+
)) })
|
|
1178
1197
|
] }) });
|
|
1179
1198
|
}
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
}
|
|
1199
|
+
var SOCIAL_ICONS = [
|
|
1200
|
+
{ key: "facebook", label: "Facebook", d: "M18 2h-3a5 5 0 0 0-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 0 1 1-1h3z" },
|
|
1201
|
+
{ key: "twitter", label: "X", d: "M18 6L6 18M6 6l12 12" },
|
|
1202
|
+
{ key: "instagram", label: "Instagram", d: "M16 11.37A4 4 0 1 1 12.63 8 4 4 0 0 1 16 11.37zm1.5-4.87h.01M6.5 2h11A4.5 4.5 0 0 1 22 6.5v11A4.5 4.5 0 0 1 17.5 22h-11A4.5 4.5 0 0 1 2 17.5v-11A4.5 4.5 0 0 1 6.5 2z" },
|
|
1203
|
+
{ key: "linkedin", label: "LinkedIn", d: "M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-4 0v7h-4v-7a6 6 0 0 1 6-6zM2 9h4v12H2zm2-3a2 2 0 1 1 0-4 2 2 0 0 1 0 4z" },
|
|
1204
|
+
{ key: "youtube", label: "YouTube", d: "M22.54 6.42a2.78 2.78 0 0 0-1.94-1.96C18.88 4 12 4 12 4s-6.88 0-8.6.46a2.78 2.78 0 0 0-1.94 1.96A29 29 0 0 0 1 12a29 29 0 0 0 .46 5.58 2.78 2.78 0 0 0 1.94 1.96C5.12 20 12 20 12 20s6.88 0 8.6-.46a2.78 2.78 0 0 0 1.94-1.96A29 29 0 0 0 23 12a29 29 0 0 0-.46-5.58zM9.75 15.02V8.98L15.5 12l-5.75 3.02z" }
|
|
1205
|
+
];
|
|
1206
|
+
function renderFooterSection(sec, mutedLink) {
|
|
1207
|
+
if (sec.type === "brand") {
|
|
1208
|
+
if (!sec.logo_url && !sec.tagline) return null;
|
|
1209
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "0.75rem" }, children: [
|
|
1210
|
+
sec.logo_url && /* @__PURE__ */ jsxRuntime.jsx("img", { src: sec.logo_url, alt: "Logo", style: { height: "32px", width: "auto", objectFit: "contain" } }),
|
|
1211
|
+
sec.tagline && /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: "0.875rem", color: "var(--muted-foreground, #6b7280)", lineHeight: 1.6, margin: 0 }, children: sec.tagline })
|
|
1212
|
+
] });
|
|
1195
1213
|
}
|
|
1196
|
-
if (
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1214
|
+
if (sec.type === "links") {
|
|
1215
|
+
const links = (sec.links || []).filter((l) => l.label);
|
|
1216
|
+
if (!links.length && !sec.title) return null;
|
|
1217
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1218
|
+
sec.title && /* @__PURE__ */ jsxRuntime.jsx("h4", { style: { fontWeight: 600, fontSize: "0.875rem", margin: "0 0 0.875rem", color: "var(--foreground, #111)" }, children: sec.title }),
|
|
1219
|
+
/* @__PURE__ */ jsxRuntime.jsx("ul", { style: { listStyle: "none", margin: 0, padding: 0, display: "flex", flexDirection: "column", gap: "0.5rem" }, children: links.map((link) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1220
|
+
"a",
|
|
1221
|
+
{
|
|
1222
|
+
href: link.url || "#",
|
|
1223
|
+
target: link.external ? "_blank" : void 0,
|
|
1224
|
+
rel: link.external ? "noopener noreferrer" : void 0,
|
|
1225
|
+
style: mutedLink,
|
|
1226
|
+
children: link.label
|
|
1227
|
+
}
|
|
1228
|
+
) }, link.id || link.label)) })
|
|
1229
|
+
] });
|
|
1230
|
+
}
|
|
1231
|
+
if (sec.type === "contact") {
|
|
1232
|
+
if (!sec.email && !sec.phone && !sec.address) return null;
|
|
1233
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1234
|
+
sec.title && /* @__PURE__ */ jsxRuntime.jsx("h4", { style: { fontWeight: 600, fontSize: "0.875rem", margin: "0 0 0.875rem", color: "var(--foreground, #111)" }, children: sec.title }),
|
|
1235
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "0.4rem" }, children: [
|
|
1236
|
+
sec.email && /* @__PURE__ */ jsxRuntime.jsx("a", { href: `mailto:${sec.email}`, style: mutedLink, children: sec.email }),
|
|
1237
|
+
sec.phone && /* @__PURE__ */ jsxRuntime.jsx("a", { href: `tel:${sec.phone}`, style: mutedLink, children: sec.phone }),
|
|
1238
|
+
sec.address && /* @__PURE__ */ jsxRuntime.jsx("p", { style: { ...mutedLink, whiteSpace: "pre-line", margin: 0 }, children: sec.address })
|
|
1239
|
+
] })
|
|
1240
|
+
] });
|
|
1241
|
+
}
|
|
1242
|
+
if (sec.type === "social") {
|
|
1243
|
+
const active = SOCIAL_ICONS.filter((s) => sec[s.key]?.trim());
|
|
1244
|
+
if (!active.length) return null;
|
|
1245
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1246
|
+
sec.title && /* @__PURE__ */ jsxRuntime.jsx("h4", { style: { fontWeight: 600, fontSize: "0.875rem", margin: "0 0 0.875rem", color: "var(--foreground, #111)" }, children: sec.title }),
|
|
1247
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: "0.625rem" }, children: active.map((s) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1248
|
+
"a",
|
|
1249
|
+
{
|
|
1250
|
+
href: sec[s.key],
|
|
1251
|
+
target: "_blank",
|
|
1252
|
+
rel: "noopener noreferrer",
|
|
1253
|
+
"aria-label": s.label,
|
|
1254
|
+
style: { display: "flex", alignItems: "center", justifyContent: "center", width: "34px", height: "34px", borderRadius: "8px", border: "1px solid var(--border, #e5e7eb)", color: "var(--muted-foreground, #6b7280)", textDecoration: "none" },
|
|
1255
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: s.d }) })
|
|
1256
|
+
},
|
|
1257
|
+
s.key
|
|
1258
|
+
)) })
|
|
1259
|
+
] });
|
|
1260
|
+
}
|
|
1261
|
+
return null;
|
|
1206
1262
|
}
|
|
1207
1263
|
function CMSFooter({ footer }) {
|
|
1208
|
-
|
|
1209
|
-
const structuredBlock = Array.isArray(footer.blocks) && footer.blocks[0]?.type === "__structured__" ? footer.blocks[0].content : null;
|
|
1210
|
-
const sd = footer.sectionData ?? structuredBlock;
|
|
1264
|
+
const sd = getSD(footer);
|
|
1211
1265
|
if (!sd) return null;
|
|
1212
|
-
const
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1266
|
+
const sections = Array.isArray(sd.sections) ? sd.sections : [];
|
|
1267
|
+
let renderSections = sections;
|
|
1268
|
+
if (renderSections.length === 0 && (sd.brand || sd.columns || sd.contact || sd.social || sd.legal)) {
|
|
1269
|
+
renderSections = [];
|
|
1270
|
+
if (sd.brand?.logo_url || sd.brand?.tagline) renderSections.push({ id: "b", type: "brand", ...sd.brand });
|
|
1271
|
+
(sd.columns || []).forEach((col) => renderSections.push({ id: col.id || col.title, type: "links", title: col.title, links: col.links }));
|
|
1272
|
+
if (sd.contact?.email || sd.contact?.phone) renderSections.push({ id: "c", type: "contact", ...sd.contact });
|
|
1273
|
+
if (Object.values(sd.social || {}).some(Boolean)) renderSections.push({ id: "s", type: "social", ...sd.social });
|
|
1274
|
+
}
|
|
1275
|
+
const legalSec = sd.sections ? sd.sections.find((s) => s.type === "legal") : sd.legal;
|
|
1276
|
+
const mainSections = renderSections.filter((s) => s.type !== "legal");
|
|
1277
|
+
if (mainSections.length === 0 && !legalSec) return null;
|
|
1278
|
+
const gridCount = mainSections.length;
|
|
1279
|
+
const gridCols = gridCount <= 1 ? "1fr" : gridCount === 2 ? "repeat(2,1fr)" : gridCount === 3 ? "repeat(3,1fr)" : "repeat(4,1fr)";
|
|
1280
|
+
const mutedLink = { fontSize: "0.875rem", color: "var(--muted-foreground, #6b7280)", textDecoration: "none" };
|
|
1281
|
+
const copyright = legalSec?.copyright || "";
|
|
1282
|
+
const legalLinks = legalSec?.legal_links || legalSec?.links || [];
|
|
1283
|
+
const hasBottom = copyright || legalLinks.filter((l) => l.label).length > 0;
|
|
1284
|
+
return /* @__PURE__ */ jsxRuntime.jsx("footer", { style: { borderTop: "1px solid var(--border, #e5e7eb)", background: "var(--muted, #f9fafb)", marginTop: "4rem" }, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { maxWidth: "1200px", margin: "0 auto", padding: "3rem 1.5rem 1.5rem" }, children: [
|
|
1285
|
+
mainSections.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "grid", gridTemplateColumns: gridCols, gap: "2.5rem", marginBottom: hasBottom ? "2.5rem" : 0 }, children: mainSections.map((sec) => {
|
|
1286
|
+
const rendered = renderFooterSection(sec, mutedLink);
|
|
1287
|
+
return rendered ? /* @__PURE__ */ jsxRuntime.jsx("div", { children: rendered }, sec.id) : null;
|
|
1288
|
+
}) }),
|
|
1289
|
+
hasBottom && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { borderTop: "1px solid var(--border, #e5e7eb)", paddingTop: "1.25rem", display: "flex", flexWrap: "wrap", alignItems: "center", justifyContent: "space-between", gap: "1rem" }, children: [
|
|
1290
|
+
copyright && /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: "0.8rem", color: "var(--muted-foreground, #6b7280)", margin: 0 }, children: copyright }),
|
|
1291
|
+
legalLinks.filter((l) => l.label).length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: "1.25rem" }, children: legalLinks.filter((l) => l.label).map((link) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1292
|
+
"a",
|
|
1293
|
+
{
|
|
1294
|
+
href: link.url || "#",
|
|
1295
|
+
target: link.external ? "_blank" : void 0,
|
|
1296
|
+
rel: link.external ? "noopener noreferrer" : void 0,
|
|
1297
|
+
style: { fontSize: "0.8rem", color: "var(--muted-foreground, #6b7280)", textDecoration: "none" },
|
|
1298
|
+
children: link.label
|
|
1299
|
+
},
|
|
1300
|
+
link.id || link.label
|
|
1301
|
+
)) })
|
|
1248
1302
|
] })
|
|
1249
1303
|
] }) });
|
|
1250
1304
|
}
|