bsmnt 0.3.3 → 0.4.1
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/dist/helpers/integrate/merge-config.js +1 -1
- package/dist/helpers/integrate/merge-config.js.map +1 -1
- package/dist/helpers/integrate/merge-orchestrator.d.ts.map +1 -1
- package/dist/helpers/integrate/merge-orchestrator.js +5 -5
- package/dist/helpers/integrate/merge-orchestrator.js.map +1 -1
- package/dist/helpers/integrate/sanity/config.d.ts.map +1 -1
- package/dist/helpers/integrate/sanity/config.js +2 -1
- package/dist/helpers/integrate/sanity/config.js.map +1 -1
- package/dist/helpers/integrate/sanity/mergers/sitemap-merger.d.ts +1 -3
- package/dist/helpers/integrate/sanity/mergers/sitemap-merger.d.ts.map +1 -1
- package/dist/helpers/integrate/sanity/mergers/sitemap-merger.js +117 -76
- package/dist/helpers/integrate/sanity/mergers/sitemap-merger.js.map +1 -1
- package/package.json +1 -1
- package/src/helpers/integrate/sanity/files/app/api/blog/[slug]/route.ts +2 -1
- package/src/helpers/integrate/sanity/files/app/api/revalidate/route.ts +4 -1
- package/src/helpers/integrate/sanity/files/app/blog/[slug]/page.tsx +3 -1
- package/src/helpers/integrate/sanity/files/app/layout.tsx +2 -2
- package/src/helpers/integrate/sanity/files/app/sitemap.md/route.ts +29 -18
- package/src/helpers/integrate/sanity/files/lib/integrations/sanity/WEBHOOK-SETUP.md +74 -0
- package/src/helpers/integrate/sanity/files/lib/integrations/sanity/env.ts +4 -2
- package/src/helpers/integrate/sanity/files/lib/integrations/sanity/queries.ts +42 -0
- package/src/helpers/integrate/sanity/files/lib/integrations/sanity/sitemap.ts +90 -0
- package/src/helpers/integrate/sanity/files/lib/utils/metadata.ts +2 -2
- package/src/helpers/integrate/sanity/files/lib/utils/url.ts +23 -0
- package/src/templates/next-default/.env.example +3 -3
- package/src/templates/next-default/app/layout.tsx +2 -2
- package/src/templates/next-default/app/robots.ts +2 -2
- package/src/templates/next-default/app/sitemap.xml/route.ts +51 -0
- package/src/templates/next-default/lib/utils/metadata.ts +2 -2
- package/src/templates/next-default/lib/utils/url.ts +16 -0
- package/src/templates/next-experiments/.env.example +3 -3
- package/src/templates/next-experiments/app/layout.tsx +2 -2
- package/src/templates/next-experiments/app/robots.ts +0 -4
- package/src/templates/next-experiments/lib/utils/metadata.ts +2 -2
- package/src/templates/next-experiments/lib/utils/url.ts +16 -0
- package/src/templates/next-pagebuilder/app/(content)/[[...slug]]/page.tsx +17 -8
- package/src/templates/next-pagebuilder/app/(content)/layout.tsx +19 -7
- package/src/templates/next-pagebuilder/app/actions/refresh.ts +5 -0
- package/src/templates/next-pagebuilder/components/layout/footer/index.tsx +15 -19
- package/src/templates/next-pagebuilder/components/layout/header/index.tsx +3 -5
- package/src/templates/next-pagebuilder/components/layout/json-ld/index.tsx +11 -10
- package/src/templates/next-pagebuilder/components/layout/wrapper/index.tsx +14 -4
- package/src/templates/next-pagebuilder/components/page-builder/components/{post-collection → content-collection}/content-card.tsx +3 -5
- package/src/templates/next-pagebuilder/components/page-builder/components/content-collection/content-filters.tsx +93 -0
- package/src/templates/next-pagebuilder/components/page-builder/components/{post-collection → content-collection}/content-grid.tsx +7 -9
- package/src/templates/next-pagebuilder/components/page-builder/components/content-collection/content-pagination-nav.tsx +71 -0
- package/src/templates/next-pagebuilder/components/page-builder/components/content-collection/index.tsx +212 -0
- package/src/templates/next-pagebuilder/components/page-builder/components/{post-collection → content-collection}/types.ts +5 -4
- package/src/templates/next-pagebuilder/components/page-builder/renderer.tsx +13 -5
- package/src/templates/next-pagebuilder/components/page-document/index.tsx +9 -4
- package/src/templates/next-pagebuilder/components/sanity/visual-editing.tsx +2 -1
- package/src/templates/next-pagebuilder/lib/integrations/sanity/constants.ts +1 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/fetchers/layout.ts +17 -18
- package/src/templates/next-pagebuilder/lib/integrations/sanity/live/index.tsx +29 -2
- package/src/templates/next-pagebuilder/lib/integrations/sanity/presentation.ts +118 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/queries.ts +144 -31
- package/src/templates/next-pagebuilder/lib/integrations/sanity/sanity.config.ts +5 -100
- package/src/templates/next-pagebuilder/next.config.ts +3 -0
- package/src/templates/next-pagebuilder/package.json +1 -2
- package/src/templates/next-webgl/.env.example +3 -3
- package/src/templates/next-webgl/app/layout.tsx +2 -2
- package/src/templates/next-webgl/app/robots.ts +2 -2
- package/src/templates/next-webgl/app/sitemap.xml/route.ts +51 -0
- package/src/templates/next-webgl/lib/utils/metadata.ts +2 -2
- package/src/templates/next-webgl/lib/utils/url.ts +16 -0
- package/src/helpers/integrate/sanity/files/app/sitemap.ts +0 -61
- package/src/templates/next-default/app/sitemap.ts +0 -16
- package/src/templates/next-experiments/app/sitemap.ts +0 -16
- package/src/templates/next-pagebuilder/components/page-builder/components/post-collection/index.tsx +0 -28
- package/src/templates/next-webgl/app/sitemap.ts +0 -16
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import type { Observable } from "rxjs"
|
|
2
|
+
import { map } from "rxjs/operators"
|
|
3
|
+
import type { DocumentStore } from "sanity"
|
|
4
|
+
import {
|
|
5
|
+
type DocumentLocationsState,
|
|
6
|
+
defineDocuments,
|
|
7
|
+
presentationTool,
|
|
8
|
+
} from "sanity/presentation"
|
|
9
|
+
import { HOMEPAGE_DOCUMENT_ID } from "./constants"
|
|
10
|
+
import { previewURL } from "./env"
|
|
11
|
+
|
|
12
|
+
const MAX_FOLDER_DEPTH = 6
|
|
13
|
+
|
|
14
|
+
const buildNestedSlugExpression = (
|
|
15
|
+
parentReferenceField: string,
|
|
16
|
+
leafSlugField: string
|
|
17
|
+
) => {
|
|
18
|
+
const cases = Array.from({ length: MAX_FOLDER_DEPTH }, (_, index) => {
|
|
19
|
+
const depth = MAX_FOLDER_DEPTH - index
|
|
20
|
+
const parentSlugRefs = Array.from({ length: depth }, (_, parentIndex) => {
|
|
21
|
+
const remainingDepth = depth - parentIndex - 1
|
|
22
|
+
return `${parentReferenceField}${"->parentFolder".repeat(remainingDepth)}->slug.current`
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const pathDefinedChecks = parentSlugRefs
|
|
26
|
+
.map((field) => `defined(${field})`)
|
|
27
|
+
.join(" && ")
|
|
28
|
+
|
|
29
|
+
const childPath = [...parentSlugRefs, leafSlugField].join(' + "/" + ')
|
|
30
|
+
const folderRootPath = parentSlugRefs.join(' + "/" + ')
|
|
31
|
+
|
|
32
|
+
return [
|
|
33
|
+
`${pathDefinedChecks} && ${leafSlugField} == "/" => ${folderRootPath}`,
|
|
34
|
+
`${pathDefinedChecks} && defined(${leafSlugField}) => ${childPath}`,
|
|
35
|
+
]
|
|
36
|
+
}).flat()
|
|
37
|
+
|
|
38
|
+
return `select(${cases.join(",\n ")},${leafSlugField})`
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const pageResolvedSlugExpression = buildNestedSlugExpression(
|
|
42
|
+
"pageFolder",
|
|
43
|
+
"slug.current"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
const CONTENT_TYPE_PREFIXES: Record<string, string> = {
|
|
47
|
+
blogContent: "blog",
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const resolveSlugLocation = <T extends { title?: string }>(
|
|
51
|
+
documentStore: DocumentStore,
|
|
52
|
+
id: string,
|
|
53
|
+
projection: string,
|
|
54
|
+
toHref: (doc: T) => string | null,
|
|
55
|
+
fallbackTitle: string
|
|
56
|
+
): Observable<DocumentLocationsState> =>
|
|
57
|
+
documentStore
|
|
58
|
+
.listenQuery(`*[_id == $id][0]{ ${projection} }`, { id }, {})
|
|
59
|
+
.pipe(
|
|
60
|
+
map((doc: T | null) => {
|
|
61
|
+
const href = doc ? toHref(doc) : null
|
|
62
|
+
if (!href) return { locations: [] }
|
|
63
|
+
return { locations: [{ title: doc?.title || fallbackTitle, href }] }
|
|
64
|
+
})
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
export const presentation = presentationTool({
|
|
68
|
+
name: "preview",
|
|
69
|
+
title: "Preview",
|
|
70
|
+
resolve: {
|
|
71
|
+
mainDocuments: defineDocuments([
|
|
72
|
+
{
|
|
73
|
+
route: "/",
|
|
74
|
+
filter: `_type == "page" && _id == "${HOMEPAGE_DOCUMENT_ID}"`,
|
|
75
|
+
},
|
|
76
|
+
...Object.entries(CONTENT_TYPE_PREFIXES).map(([type, prefix]) => ({
|
|
77
|
+
route: `/${prefix}/:slug`,
|
|
78
|
+
filter: `_type == "${type}" && slug.current == $slug`,
|
|
79
|
+
})),
|
|
80
|
+
{
|
|
81
|
+
route: "/:slug+",
|
|
82
|
+
filter: `_type == "page" && ${pageResolvedSlugExpression} == $slug`,
|
|
83
|
+
},
|
|
84
|
+
]),
|
|
85
|
+
locations: (params, { documentStore }) => {
|
|
86
|
+
if (params.type === "page") {
|
|
87
|
+
if (params.id === HOMEPAGE_DOCUMENT_ID) {
|
|
88
|
+
return { locations: [{ title: "Homepage", href: "/" }] }
|
|
89
|
+
}
|
|
90
|
+
return resolveSlugLocation<{ title?: string; resolvedSlug?: string }>(
|
|
91
|
+
documentStore,
|
|
92
|
+
params.id,
|
|
93
|
+
`title, "resolvedSlug": ${pageResolvedSlugExpression}`,
|
|
94
|
+
(doc) => (doc.resolvedSlug ? `/${doc.resolvedSlug}` : null),
|
|
95
|
+
"Page"
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const prefix = CONTENT_TYPE_PREFIXES[params.type]
|
|
100
|
+
if (!prefix) return null
|
|
101
|
+
|
|
102
|
+
return resolveSlugLocation<{ title?: string; slug?: string }>(
|
|
103
|
+
documentStore,
|
|
104
|
+
params.id,
|
|
105
|
+
`title, "slug": slug.current`,
|
|
106
|
+
(doc) => (doc.slug ? `/${prefix}/${doc.slug}` : null),
|
|
107
|
+
"Untitled"
|
|
108
|
+
)
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
previewUrl: {
|
|
112
|
+
origin: previewURL,
|
|
113
|
+
draftMode: {
|
|
114
|
+
enable: "/api/draft-mode/enable",
|
|
115
|
+
disable: "/api/draft-mode/disable",
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
})
|
|
@@ -97,6 +97,13 @@ const pageProjection = `
|
|
|
97
97
|
_updatedAt
|
|
98
98
|
`
|
|
99
99
|
|
|
100
|
+
const pageMetadataProjection = `
|
|
101
|
+
_id,
|
|
102
|
+
title,
|
|
103
|
+
metadata,
|
|
104
|
+
_updatedAt
|
|
105
|
+
`
|
|
106
|
+
|
|
100
107
|
export const PAGE_QUERY = defineQuery(`
|
|
101
108
|
*[
|
|
102
109
|
_type == "page" &&
|
|
@@ -109,6 +116,18 @@ export const PAGE_QUERY = defineQuery(`
|
|
|
109
116
|
}
|
|
110
117
|
`)
|
|
111
118
|
|
|
119
|
+
export const PAGE_METADATA_QUERY = defineQuery(`
|
|
120
|
+
*[
|
|
121
|
+
_type == "page" &&
|
|
122
|
+
(
|
|
123
|
+
($slug == null && !defined(pageFolder._ref) && (!defined(slug.current) || slug.current == "" || slug.current == "/")) ||
|
|
124
|
+
${pageResolvedSlugExpression} == $slug
|
|
125
|
+
)
|
|
126
|
+
][0] {
|
|
127
|
+
${pageMetadataProjection}
|
|
128
|
+
}
|
|
129
|
+
`)
|
|
130
|
+
|
|
112
131
|
export const ALL_PAGE_SLUGS_QUERY = defineQuery(`
|
|
113
132
|
*[_type == "page" && (defined(pageFolder->slug.current) || defined(slug.current))] | order(title asc) {
|
|
114
133
|
title,
|
|
@@ -193,6 +212,68 @@ export const ALL_BLOG_ARTICLES_QUERY = defineQuery(`
|
|
|
193
212
|
}
|
|
194
213
|
`)
|
|
195
214
|
|
|
215
|
+
export const BLOG_ARTICLES_PAGINATED_QUERY = defineQuery(`
|
|
216
|
+
*[
|
|
217
|
+
_type == "page" &&
|
|
218
|
+
pageFolder->slug.current == "blog" &&
|
|
219
|
+
defined(slug.current) &&
|
|
220
|
+
slug.current != "" &&
|
|
221
|
+
slug.current != "/" &&
|
|
222
|
+
(
|
|
223
|
+
!defined($category) ||
|
|
224
|
+
$category == "" ||
|
|
225
|
+
$category in pageBuilder[_ref in *[_type == "blogContent"]._id][0]->categories[]->slug.current
|
|
226
|
+
)
|
|
227
|
+
] | order(_updatedAt desc)[$offset...$offset + $limit] {
|
|
228
|
+
_id,
|
|
229
|
+
title,
|
|
230
|
+
"slug": slug,
|
|
231
|
+
"resolvedSlug": ${pageResolvedSlugExpression},
|
|
232
|
+
"blogContent": pageBuilder[_ref in *[_type == "blogContent"]._id][0]->{
|
|
233
|
+
thumbnail{
|
|
234
|
+
...,
|
|
235
|
+
alt
|
|
236
|
+
},
|
|
237
|
+
categories[]->{
|
|
238
|
+
_id,
|
|
239
|
+
title,
|
|
240
|
+
slug
|
|
241
|
+
},
|
|
242
|
+
date,
|
|
243
|
+
author->{
|
|
244
|
+
_id,
|
|
245
|
+
name,
|
|
246
|
+
avatar
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
metadata,
|
|
250
|
+
_updatedAt
|
|
251
|
+
}
|
|
252
|
+
`)
|
|
253
|
+
|
|
254
|
+
export const BLOG_ARTICLES_COUNT_QUERY = defineQuery(`
|
|
255
|
+
count(*[
|
|
256
|
+
_type == "page" &&
|
|
257
|
+
pageFolder->slug.current == "blog" &&
|
|
258
|
+
defined(slug.current) &&
|
|
259
|
+
slug.current != "" &&
|
|
260
|
+
slug.current != "/" &&
|
|
261
|
+
(
|
|
262
|
+
!defined($category) ||
|
|
263
|
+
$category == "" ||
|
|
264
|
+
$category in pageBuilder[_ref in *[_type == "blogContent"]._id][0]->categories[]->slug.current
|
|
265
|
+
)
|
|
266
|
+
])
|
|
267
|
+
`)
|
|
268
|
+
|
|
269
|
+
export const ALL_BLOG_CATEGORIES_QUERY = defineQuery(`
|
|
270
|
+
*[_type == "blogCategory"] | order(title asc) {
|
|
271
|
+
_id,
|
|
272
|
+
title,
|
|
273
|
+
slug
|
|
274
|
+
}
|
|
275
|
+
`)
|
|
276
|
+
|
|
196
277
|
// Company Data
|
|
197
278
|
export const COMPANY_DATA_QUERY = defineQuery(`
|
|
198
279
|
*[_type == "companyData"][0]{
|
|
@@ -235,47 +316,79 @@ const navMegaMenuProjection = `
|
|
|
235
316
|
}
|
|
236
317
|
`
|
|
237
318
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
319
|
+
const navbarProjection = `
|
|
320
|
+
logo{
|
|
321
|
+
...,
|
|
322
|
+
asset->{
|
|
323
|
+
_id,
|
|
324
|
+
url,
|
|
325
|
+
metadata { dimensions }
|
|
326
|
+
}
|
|
327
|
+
},
|
|
328
|
+
navigationItems[]{
|
|
329
|
+
_key,
|
|
330
|
+
title,
|
|
331
|
+
itemType,
|
|
332
|
+
${navLinkProjection},
|
|
333
|
+
megaMenu{
|
|
334
|
+
${navMegaMenuProjection}
|
|
335
|
+
}
|
|
336
|
+
},
|
|
337
|
+
ctaButtons[]{
|
|
338
|
+
_key,
|
|
339
|
+
label,
|
|
340
|
+
"link": link[0]{
|
|
341
|
+
${linkItemProjection}
|
|
256
342
|
},
|
|
257
|
-
|
|
343
|
+
variant
|
|
344
|
+
}
|
|
345
|
+
`
|
|
346
|
+
|
|
347
|
+
const footerProjection = `
|
|
348
|
+
links[]{
|
|
349
|
+
_key,
|
|
350
|
+
title,
|
|
351
|
+
items[]{
|
|
258
352
|
_key,
|
|
259
353
|
label,
|
|
260
|
-
|
|
261
|
-
${linkItemProjection}
|
|
262
|
-
},
|
|
263
|
-
variant
|
|
354
|
+
href
|
|
264
355
|
}
|
|
265
356
|
}
|
|
357
|
+
`
|
|
358
|
+
|
|
359
|
+
const companyDataProjection = `
|
|
360
|
+
socialLinks[]{
|
|
361
|
+
_key,
|
|
362
|
+
name,
|
|
363
|
+
icon,
|
|
364
|
+
url,
|
|
365
|
+
label
|
|
366
|
+
}
|
|
367
|
+
`
|
|
368
|
+
|
|
369
|
+
export const NAVBAR_QUERY = defineQuery(`
|
|
370
|
+
*[_type == "navbar"][0]{
|
|
371
|
+
${navbarProjection}
|
|
372
|
+
}
|
|
266
373
|
`)
|
|
267
374
|
|
|
268
375
|
// Footer
|
|
269
376
|
export const FOOTER_QUERY = defineQuery(`
|
|
270
377
|
*[_type == "footer"][0]{
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
}
|
|
378
|
+
${footerProjection}
|
|
379
|
+
}
|
|
380
|
+
`)
|
|
381
|
+
|
|
382
|
+
export const SITE_LAYOUT_QUERY = defineQuery(`
|
|
383
|
+
{
|
|
384
|
+
"navbar": *[_type == "navbar"][0]{
|
|
385
|
+
${navbarProjection}
|
|
386
|
+
},
|
|
387
|
+
"footer": *[_type == "footer"][0]{
|
|
388
|
+
${footerProjection}
|
|
389
|
+
},
|
|
390
|
+
"companyData": *[_type == "companyData"][0]{
|
|
391
|
+
${companyDataProjection}
|
|
279
392
|
}
|
|
280
393
|
}
|
|
281
394
|
`)
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
import { table } from "@sanity/table"
|
|
2
2
|
import { defineConfig } from "sanity"
|
|
3
|
-
import {
|
|
4
|
-
defineDocuments,
|
|
5
|
-
defineLocations,
|
|
6
|
-
presentationTool,
|
|
7
|
-
} from "sanity/presentation"
|
|
8
3
|
import { structureTool } from "sanity/structure"
|
|
9
4
|
import { media } from "sanity-plugin-media"
|
|
10
5
|
import { createConfirmPublishAction } from "./confirm-publish-action"
|
|
11
|
-
import {
|
|
6
|
+
import { HOMEPAGE_DOCUMENT_ID } from "./constants"
|
|
7
|
+
import { dataset, projectId } from "./env"
|
|
8
|
+
import { presentation } from "./presentation"
|
|
12
9
|
import { schema } from "./schemas"
|
|
13
10
|
import { singletonComponentTypes } from "./singletons"
|
|
14
11
|
import { structure } from "./structure"
|
|
@@ -23,46 +20,7 @@ const singletonDocumentActions = new Set([
|
|
|
23
20
|
|
|
24
21
|
const isSingletonDocument = (schemaType: string, documentId?: string) =>
|
|
25
22
|
singletonComponentTypes.has(schemaType) ||
|
|
26
|
-
(schemaType === "page" && documentId ===
|
|
27
|
-
|
|
28
|
-
const MAX_FOLDER_DEPTH = 6
|
|
29
|
-
|
|
30
|
-
const buildNestedSlugExpression = (
|
|
31
|
-
parentReferenceField: string,
|
|
32
|
-
leafSlugField: string
|
|
33
|
-
) => {
|
|
34
|
-
const cases = Array.from({ length: MAX_FOLDER_DEPTH }, (_, index) => {
|
|
35
|
-
const depth = MAX_FOLDER_DEPTH - index
|
|
36
|
-
const parentSlugRefs = Array.from({ length: depth }, (_, parentIndex) => {
|
|
37
|
-
const remainingDepth = depth - parentIndex - 1
|
|
38
|
-
return `${parentReferenceField}${"->parentFolder".repeat(remainingDepth)}->slug.current`
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
const pathDefinedChecks = parentSlugRefs
|
|
42
|
-
.map((field) => `defined(${field})`)
|
|
43
|
-
.join(" && ")
|
|
44
|
-
|
|
45
|
-
const childPath = [...parentSlugRefs, leafSlugField].join(' + "/" + ')
|
|
46
|
-
const folderRootPath = parentSlugRefs.join(' + "/" + ')
|
|
47
|
-
|
|
48
|
-
return [
|
|
49
|
-
`${pathDefinedChecks} && ${leafSlugField} == "/" => ${folderRootPath}`,
|
|
50
|
-
`${pathDefinedChecks} && defined(${leafSlugField}) => ${childPath}`,
|
|
51
|
-
]
|
|
52
|
-
}).flat()
|
|
53
|
-
|
|
54
|
-
return `
|
|
55
|
-
select(
|
|
56
|
-
${cases.join(",\n ")},
|
|
57
|
-
${leafSlugField}
|
|
58
|
-
)
|
|
59
|
-
`
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const pageResolvedSlugExpression = buildNestedSlugExpression(
|
|
63
|
-
"pageFolder",
|
|
64
|
-
"slug.current"
|
|
65
|
-
)
|
|
23
|
+
(schemaType === "page" && documentId === HOMEPAGE_DOCUMENT_ID)
|
|
66
24
|
|
|
67
25
|
export default defineConfig({
|
|
68
26
|
basePath: "/studio",
|
|
@@ -151,60 +109,7 @@ export default defineConfig({
|
|
|
151
109
|
},
|
|
152
110
|
plugins: [
|
|
153
111
|
structureTool({ structure }),
|
|
154
|
-
|
|
155
|
-
presentationTool({
|
|
156
|
-
name: "preview",
|
|
157
|
-
title: "Preview",
|
|
158
|
-
resolve: {
|
|
159
|
-
// Map routes to documents and GROQ filters
|
|
160
|
-
mainDocuments: defineDocuments([
|
|
161
|
-
{
|
|
162
|
-
route: "/",
|
|
163
|
-
filter: '_type == "page" && _id == "page-homepage"',
|
|
164
|
-
},
|
|
165
|
-
// Page builder pages - all other slugs (supports nested paths)
|
|
166
|
-
{
|
|
167
|
-
route: "/:slug+",
|
|
168
|
-
filter: `_type == "page" && ${pageResolvedSlugExpression} == $slug`,
|
|
169
|
-
},
|
|
170
|
-
]),
|
|
171
|
-
locations: {
|
|
172
|
-
page: defineLocations({
|
|
173
|
-
select: {
|
|
174
|
-
_id: "_id",
|
|
175
|
-
title: "title",
|
|
176
|
-
slug: "slug.current",
|
|
177
|
-
resolvedSlug: pageResolvedSlugExpression,
|
|
178
|
-
},
|
|
179
|
-
resolve: (doc) => {
|
|
180
|
-
if (doc?._id === "page-homepage") {
|
|
181
|
-
return {
|
|
182
|
-
locations: [{ title: doc?.title || "Homepage", href: "/" }],
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
if (doc?.resolvedSlug) {
|
|
186
|
-
return {
|
|
187
|
-
locations: [
|
|
188
|
-
{
|
|
189
|
-
title: doc?.title || "Page",
|
|
190
|
-
href: `/${doc.resolvedSlug}`,
|
|
191
|
-
},
|
|
192
|
-
],
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
return { locations: [] }
|
|
196
|
-
},
|
|
197
|
-
}),
|
|
198
|
-
},
|
|
199
|
-
},
|
|
200
|
-
previewUrl: {
|
|
201
|
-
origin: previewURL,
|
|
202
|
-
draftMode: {
|
|
203
|
-
enable: "/api/draft-mode/enable",
|
|
204
|
-
disable: "/api/draft-mode/disable",
|
|
205
|
-
},
|
|
206
|
-
},
|
|
207
|
-
}),
|
|
112
|
+
presentation,
|
|
208
113
|
media(),
|
|
209
114
|
table(),
|
|
210
115
|
],
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import withBundleAnalyzer from "@next/bundle-analyzer"
|
|
2
2
|
import type { NextConfig } from "next"
|
|
3
3
|
|
|
4
|
+
|
|
5
|
+
|
|
4
6
|
const nextConfig: NextConfig = {
|
|
5
7
|
reactStrictMode: true,
|
|
6
8
|
reactCompiler: true,
|
|
@@ -62,6 +64,7 @@ const nextConfig: NextConfig = {
|
|
|
62
64
|
browserToTerminal: true,
|
|
63
65
|
},
|
|
64
66
|
experimental: {
|
|
67
|
+
prefetchInlining: true,
|
|
65
68
|
optimizePackageImports: [
|
|
66
69
|
"@react-three/drei",
|
|
67
70
|
"@react-three/fiber",
|
|
@@ -39,13 +39,11 @@
|
|
|
39
39
|
"react-use": "^17.6.0",
|
|
40
40
|
"sanity": "^5.17.1",
|
|
41
41
|
"sanity-plugin-media": "^4.1.1",
|
|
42
|
-
"schema-dts": "^2.0.0",
|
|
43
42
|
"tailwind-merge": "^3.5.0",
|
|
44
43
|
"zod": "^4.3.6"
|
|
45
44
|
},
|
|
46
45
|
"devDependencies": {
|
|
47
46
|
"@biomejs/biome": "2.4.8",
|
|
48
|
-
"@clack/prompts": "^1.1.0",
|
|
49
47
|
"@csstools/postcss-global-data": "^4.0.0",
|
|
50
48
|
"@next/bundle-analyzer": "16.2.1",
|
|
51
49
|
"@svgr/webpack": "^8.1.0",
|
|
@@ -59,6 +57,7 @@
|
|
|
59
57
|
"cross-env": "^10.1.0",
|
|
60
58
|
"postcss-functions": "^4.0.2",
|
|
61
59
|
"postcss-preset-env": "^11.2.0",
|
|
60
|
+
"schema-dts": "^2.0.0",
|
|
62
61
|
"tailwindcss": "^4.2.2",
|
|
63
62
|
"typescript": "^5.9.3"
|
|
64
63
|
},
|
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
# CORE (Recommended)
|
|
14
14
|
# ============================================
|
|
15
15
|
|
|
16
|
-
# Base URL
|
|
17
|
-
#
|
|
18
|
-
#
|
|
16
|
+
# Base URL (sitemaps, OG tags, canonical URLs)
|
|
17
|
+
# On Vercel: auto-detected if unset. Set explicitly for custom domains.
|
|
18
|
+
# Local: http://localhost:3000
|
|
19
19
|
NEXT_PUBLIC_BASE_URL="http://localhost:3000"
|
|
20
20
|
|
|
21
21
|
# Draft mode secret for preview functionality
|
|
@@ -5,6 +5,7 @@ import { Link } from "@/components/ui/link";
|
|
|
5
5
|
import AppData from "@/package.json";
|
|
6
6
|
import "@/lib/styles/global.css";
|
|
7
7
|
import { cn } from "@/lib/styles/cn";
|
|
8
|
+
import { getBaseUrl } from "@/lib/utils/url";
|
|
8
9
|
import {
|
|
9
10
|
JsonLd,
|
|
10
11
|
generateWebSiteJsonLd,
|
|
@@ -15,8 +16,7 @@ const APP_NAME = AppData.name;
|
|
|
15
16
|
const APP_DEFAULT_TITLE = "Basement Starter";
|
|
16
17
|
const APP_TITLE_TEMPLATE = "%s - Basement Starter";
|
|
17
18
|
const APP_DESCRIPTION = AppData.description;
|
|
18
|
-
const APP_BASE_URL =
|
|
19
|
-
process.env.NEXT_PUBLIC_BASE_URL ?? "http://localhost:3000";
|
|
19
|
+
const APP_BASE_URL = getBaseUrl();
|
|
20
20
|
|
|
21
21
|
const geist = Geist({
|
|
22
22
|
subsets: ["latin"],
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { MetadataRoute } from "next";
|
|
2
|
+
import { getBaseUrl } from "@/lib/utils/url";
|
|
2
3
|
|
|
3
|
-
const APP_BASE_URL =
|
|
4
|
-
process.env.NEXT_PUBLIC_BASE_URL ?? "http://localhost:3000";
|
|
4
|
+
const APP_BASE_URL = getBaseUrl();
|
|
5
5
|
|
|
6
6
|
export default function robots(): MetadataRoute.Robots {
|
|
7
7
|
return {
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { getBaseUrl } from "@/lib/utils/url";
|
|
2
|
+
|
|
3
|
+
const BASE_URL = getBaseUrl();
|
|
4
|
+
|
|
5
|
+
export const dynamic = "force-dynamic";
|
|
6
|
+
|
|
7
|
+
interface SitemapEntry {
|
|
8
|
+
loc: string;
|
|
9
|
+
lastmod: string;
|
|
10
|
+
changefreq: string;
|
|
11
|
+
priority: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function toXml(entries: SitemapEntry[]): string {
|
|
15
|
+
const urls = entries
|
|
16
|
+
.map(
|
|
17
|
+
(entry) => `
|
|
18
|
+
<url>
|
|
19
|
+
<loc>${entry.loc}</loc>
|
|
20
|
+
<lastmod>${entry.lastmod}</lastmod>
|
|
21
|
+
<changefreq>${entry.changefreq}</changefreq>
|
|
22
|
+
<priority>${entry.priority}</priority>
|
|
23
|
+
</url>`,
|
|
24
|
+
)
|
|
25
|
+
.join("");
|
|
26
|
+
|
|
27
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
28
|
+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">${urls}
|
|
29
|
+
</urlset>`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function getStaticEntries(): SitemapEntry[] {
|
|
33
|
+
return [
|
|
34
|
+
{
|
|
35
|
+
loc: BASE_URL,
|
|
36
|
+
lastmod: new Date().toISOString(),
|
|
37
|
+
changefreq: "daily",
|
|
38
|
+
priority: 1,
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function GET() {
|
|
44
|
+
const entries: SitemapEntry[] = getStaticEntries();
|
|
45
|
+
|
|
46
|
+
return new Response(toXml(entries), {
|
|
47
|
+
headers: {
|
|
48
|
+
"Content-Type": "application/xml",
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Metadata } from "next";
|
|
2
|
+
import { getBaseUrl } from "@/lib/utils/url";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Metadata Generation Utilities
|
|
@@ -26,8 +27,7 @@ interface GenerateMetadataOptions {
|
|
|
26
27
|
authors?: string[];
|
|
27
28
|
}
|
|
28
29
|
|
|
29
|
-
const APP_BASE_URL =
|
|
30
|
-
process.env.NEXT_PUBLIC_BASE_URL ?? "http://localhost:3000";
|
|
30
|
+
const APP_BASE_URL = getBaseUrl();
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* Generate complete metadata object for pages
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves the application base URL from environment variables.
|
|
3
|
+
*
|
|
4
|
+
* Priority:
|
|
5
|
+
* 1. NEXT_PUBLIC_BASE_URL - explicit override (custom domains)
|
|
6
|
+
* 2. VERCEL_PROJECT_PRODUCTION_URL - auto-set by Vercel (production domain)
|
|
7
|
+
* 3. VERCEL_URL - auto-set by Vercel (preview/branch deploys)
|
|
8
|
+
* 4. localhost fallback
|
|
9
|
+
*/
|
|
10
|
+
export function getBaseUrl(): string {
|
|
11
|
+
if (process.env.NEXT_PUBLIC_BASE_URL) return process.env.NEXT_PUBLIC_BASE_URL;
|
|
12
|
+
if (process.env.VERCEL_PROJECT_PRODUCTION_URL)
|
|
13
|
+
return `https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}`;
|
|
14
|
+
if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`;
|
|
15
|
+
return "http://localhost:3000";
|
|
16
|
+
}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import type { MetadataRoute } from "next";
|
|
2
|
-
import { isSanityConfigured } from "@/lib/integrations/check-integration";
|
|
3
|
-
|
|
4
|
-
const APP_BASE_URL =
|
|
5
|
-
process.env.NEXT_PUBLIC_BASE_URL ?? "http://localhost:3000";
|
|
6
|
-
|
|
7
|
-
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
|
8
|
-
const baseRoutes: MetadataRoute.Sitemap = [
|
|
9
|
-
{
|
|
10
|
-
url: APP_BASE_URL,
|
|
11
|
-
lastModified: new Date(),
|
|
12
|
-
changeFrequency: "daily",
|
|
13
|
-
priority: 1,
|
|
14
|
-
},
|
|
15
|
-
];
|
|
16
|
-
|
|
17
|
-
// Only fetch Sanity articles if Sanity is configured
|
|
18
|
-
if (isSanityConfigured()) {
|
|
19
|
-
try {
|
|
20
|
-
const sanityModule = await import("@/lib/integrations/sanity/client");
|
|
21
|
-
const sanityGroq = await import("next-sanity");
|
|
22
|
-
|
|
23
|
-
const client = sanityModule?.client;
|
|
24
|
-
const groq = sanityGroq?.groq;
|
|
25
|
-
|
|
26
|
-
// Skip if client is null (shouldn't happen since we check isSanityConfigured)
|
|
27
|
-
if (!(client && groq)) return baseRoutes;
|
|
28
|
-
|
|
29
|
-
type SanityDocument = {
|
|
30
|
-
slug: { current: string };
|
|
31
|
-
_updatedAt: string;
|
|
32
|
-
metadata?: { noIndex?: boolean };
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
const articles = (await client.fetch(
|
|
36
|
-
groq`*[_type == "article" && defined(slug.current)] {
|
|
37
|
-
slug,
|
|
38
|
-
_updatedAt,
|
|
39
|
-
metadata
|
|
40
|
-
}`,
|
|
41
|
-
)) as SanityDocument[];
|
|
42
|
-
|
|
43
|
-
// Add articles to sitemap (exclude noIndex articles)
|
|
44
|
-
const articleEntries: MetadataRoute.Sitemap = articles
|
|
45
|
-
.filter((article: SanityDocument) => !article.metadata?.noIndex)
|
|
46
|
-
.map((article: SanityDocument) => ({
|
|
47
|
-
url: `${APP_BASE_URL}/blog/${article.slug.current}`,
|
|
48
|
-
lastModified: new Date(article._updatedAt),
|
|
49
|
-
changeFrequency: "weekly" as const,
|
|
50
|
-
priority: 0.7,
|
|
51
|
-
}));
|
|
52
|
-
|
|
53
|
-
return [...baseRoutes, ...articleEntries];
|
|
54
|
-
} catch (error) {
|
|
55
|
-
console.error("Error generating sitemap from Sanity:", error);
|
|
56
|
-
return baseRoutes;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return baseRoutes;
|
|
61
|
-
}
|