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,93 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { usePathname, useRouter, useSearchParams } from "next/navigation"
|
|
4
|
+
import { cn } from "@/lib/styles/cn"
|
|
5
|
+
import type { ContentCategory } from "./types"
|
|
6
|
+
|
|
7
|
+
type ContentFiltersProps = {
|
|
8
|
+
categories: ContentCategory[]
|
|
9
|
+
activeCategory: string
|
|
10
|
+
totalCount: number
|
|
11
|
+
filteredCount: number
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function FilterChip({
|
|
15
|
+
active,
|
|
16
|
+
label,
|
|
17
|
+
onClick,
|
|
18
|
+
}: {
|
|
19
|
+
active: boolean
|
|
20
|
+
label: string
|
|
21
|
+
onClick: () => void
|
|
22
|
+
}) {
|
|
23
|
+
return (
|
|
24
|
+
<button
|
|
25
|
+
type="button"
|
|
26
|
+
onClick={onClick}
|
|
27
|
+
className={cn(
|
|
28
|
+
"rounded-full border px-4 py-2 text-sm transition-colors",
|
|
29
|
+
active
|
|
30
|
+
? "border-black bg-black text-white"
|
|
31
|
+
: "border-black/10 bg-white hover:border-black/30"
|
|
32
|
+
)}
|
|
33
|
+
>
|
|
34
|
+
{label}
|
|
35
|
+
</button>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function ContentFilters({
|
|
40
|
+
categories,
|
|
41
|
+
activeCategory,
|
|
42
|
+
totalCount,
|
|
43
|
+
filteredCount,
|
|
44
|
+
}: ContentFiltersProps) {
|
|
45
|
+
const pathname = usePathname()
|
|
46
|
+
const router = useRouter()
|
|
47
|
+
const searchParams = useSearchParams()
|
|
48
|
+
|
|
49
|
+
const navigate = (category: string) => {
|
|
50
|
+
const nextParams = new URLSearchParams(searchParams.toString())
|
|
51
|
+
|
|
52
|
+
if (category) nextParams.set("category", category)
|
|
53
|
+
else nextParams.delete("category")
|
|
54
|
+
|
|
55
|
+
nextParams.delete("page")
|
|
56
|
+
|
|
57
|
+
const href = nextParams.toString()
|
|
58
|
+
? `${pathname}?${nextParams.toString()}`
|
|
59
|
+
: pathname
|
|
60
|
+
|
|
61
|
+
// biome-ignore lint/suspicious/noExplicitAny: typed routes do not accept dynamic query string helpers cleanly
|
|
62
|
+
router.push(href as any, { scroll: false })
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<div className="flex flex-col gap-4 rounded-[1.5rem] border border-black/10 bg-black/[0.02] p-4 sm:p-5">
|
|
67
|
+
<div className="flex flex-wrap gap-2">
|
|
68
|
+
<FilterChip
|
|
69
|
+
active={activeCategory === ""}
|
|
70
|
+
label="All content"
|
|
71
|
+
onClick={() => navigate("")}
|
|
72
|
+
/>
|
|
73
|
+
{categories.map((category) => {
|
|
74
|
+
const slug = category.slug?.current
|
|
75
|
+
if (!slug) return null
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<FilterChip
|
|
79
|
+
key={category._id}
|
|
80
|
+
active={activeCategory === slug}
|
|
81
|
+
label={category.title || "Untitled"}
|
|
82
|
+
onClick={() => navigate(slug)}
|
|
83
|
+
/>
|
|
84
|
+
)
|
|
85
|
+
})}
|
|
86
|
+
</div>
|
|
87
|
+
<p className="text-current/60 text-sm">
|
|
88
|
+
Showing {filteredCount} {filteredCount === 1 ? "item" : "items"}
|
|
89
|
+
{filteredCount !== totalCount ? ` out of ${totalCount}` : ""}
|
|
90
|
+
</p>
|
|
91
|
+
</div>
|
|
92
|
+
)
|
|
93
|
+
}
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import type { BlogArticleCard } from "./types"
|
|
2
1
|
import { ContentCard } from "./content-card"
|
|
2
|
+
import type { ContentItemCard } from "./types"
|
|
3
3
|
|
|
4
4
|
type ContentGridProps = {
|
|
5
|
-
|
|
5
|
+
items: ContentItemCard[]
|
|
6
6
|
start?: number
|
|
7
7
|
end?: number
|
|
8
8
|
className?: string
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export function ContentGrid({
|
|
12
|
-
|
|
12
|
+
items,
|
|
13
13
|
start = 0,
|
|
14
14
|
end,
|
|
15
15
|
className,
|
|
16
16
|
}: ContentGridProps) {
|
|
17
|
-
const slice =
|
|
17
|
+
const slice = items.slice(start, end)
|
|
18
18
|
|
|
19
19
|
if (slice.length === 0) {
|
|
20
20
|
if (start === 0) {
|
|
@@ -29,13 +29,11 @@ export function ContentGrid({
|
|
|
29
29
|
className ?? "grid grid-cols-2 gap-8 md:grid-cols-2 lg:grid-cols-3"
|
|
30
30
|
}
|
|
31
31
|
>
|
|
32
|
-
{slice.map((
|
|
33
|
-
const slug =
|
|
32
|
+
{slice.map((item) => {
|
|
33
|
+
const slug = item.resolvedSlug
|
|
34
34
|
if (!slug) return null
|
|
35
35
|
|
|
36
|
-
return
|
|
37
|
-
<ContentCard key={article._id} article={article} />
|
|
38
|
-
)
|
|
36
|
+
return <ContentCard key={item._id} article={item} />
|
|
39
37
|
})}
|
|
40
38
|
</ul>
|
|
41
39
|
)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { usePathname, useSearchParams } from "next/navigation"
|
|
4
|
+
import { Link } from "@/components/ui/link"
|
|
5
|
+
|
|
6
|
+
type ContentPaginationNavProps = {
|
|
7
|
+
currentPage: number
|
|
8
|
+
totalPages: number
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function buildPaginationHref({
|
|
12
|
+
pathname,
|
|
13
|
+
page,
|
|
14
|
+
searchParams,
|
|
15
|
+
}: {
|
|
16
|
+
pathname: string
|
|
17
|
+
page: number
|
|
18
|
+
searchParams: ReturnType<typeof useSearchParams>
|
|
19
|
+
}) {
|
|
20
|
+
const nextParams = new URLSearchParams(searchParams.toString())
|
|
21
|
+
|
|
22
|
+
if (page > 1) nextParams.set("page", String(page))
|
|
23
|
+
else nextParams.delete("page")
|
|
24
|
+
|
|
25
|
+
const query = nextParams.toString()
|
|
26
|
+
return query ? `${pathname}?${query}` : pathname
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function ContentPaginationNav({
|
|
30
|
+
currentPage,
|
|
31
|
+
totalPages,
|
|
32
|
+
}: ContentPaginationNavProps) {
|
|
33
|
+
const pathname = usePathname()
|
|
34
|
+
const searchParams = useSearchParams()
|
|
35
|
+
|
|
36
|
+
if (!pathname || totalPages <= 1) return null
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<nav className="flex flex-wrap items-center gap-2" aria-label="Pagination">
|
|
40
|
+
{currentPage > 1 ? (
|
|
41
|
+
<Link
|
|
42
|
+
href={buildPaginationHref({
|
|
43
|
+
pathname,
|
|
44
|
+
page: currentPage - 1,
|
|
45
|
+
searchParams,
|
|
46
|
+
})}
|
|
47
|
+
className="rounded-full border border-black/10 px-4 py-2 text-sm transition-colors hover:border-black/30"
|
|
48
|
+
>
|
|
49
|
+
Previous
|
|
50
|
+
</Link>
|
|
51
|
+
) : null}
|
|
52
|
+
|
|
53
|
+
<span className="text-current/60 text-sm">
|
|
54
|
+
Page {currentPage} of {totalPages}
|
|
55
|
+
</span>
|
|
56
|
+
|
|
57
|
+
{currentPage < totalPages ? (
|
|
58
|
+
<Link
|
|
59
|
+
href={buildPaginationHref({
|
|
60
|
+
pathname,
|
|
61
|
+
page: currentPage + 1,
|
|
62
|
+
searchParams,
|
|
63
|
+
})}
|
|
64
|
+
className="rounded-full border border-black/10 px-4 py-2 text-sm transition-colors hover:border-black/30"
|
|
65
|
+
>
|
|
66
|
+
Next
|
|
67
|
+
</Link>
|
|
68
|
+
) : null}
|
|
69
|
+
</nav>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { fetchSanity } from "@/lib/integrations/sanity/live"
|
|
2
|
+
import {
|
|
3
|
+
ALL_BLOG_CATEGORIES_QUERY,
|
|
4
|
+
BLOG_ARTICLES_COUNT_QUERY,
|
|
5
|
+
BLOG_ARTICLES_PAGINATED_QUERY,
|
|
6
|
+
} from "@/lib/integrations/sanity/queries"
|
|
7
|
+
import { ContentFilters } from "./content-filters"
|
|
8
|
+
import { ContentGrid } from "./content-grid"
|
|
9
|
+
import { ContentPaginationNav } from "./content-pagination-nav"
|
|
10
|
+
import type {
|
|
11
|
+
ContentCollectionBlock,
|
|
12
|
+
ContentCollectionSearchParams,
|
|
13
|
+
ContentCategory,
|
|
14
|
+
ContentItemCard,
|
|
15
|
+
SearchParams,
|
|
16
|
+
} from "./types"
|
|
17
|
+
|
|
18
|
+
type ContentCollectionProps = {
|
|
19
|
+
block: ContentCollectionBlock
|
|
20
|
+
searchParams: ContentCollectionSearchParams
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const FEATURED_COUNT = 2
|
|
24
|
+
const PAGE_SIZE = 6
|
|
25
|
+
|
|
26
|
+
const normalizePageValue = (value: string | string[] | undefined) => {
|
|
27
|
+
const page = Number(Array.isArray(value) ? value[0] : value)
|
|
28
|
+
return Number.isFinite(page) && page > 0 ? Math.floor(page) : 1
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const getCollectionParams = (searchParams: SearchParams) => ({
|
|
32
|
+
currentPage: normalizePageValue(searchParams.page),
|
|
33
|
+
activeCategory:
|
|
34
|
+
typeof searchParams.category === "string" ? searchParams.category : "",
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
const getTotalPages = ({
|
|
38
|
+
activeCategory,
|
|
39
|
+
totalCount,
|
|
40
|
+
}: {
|
|
41
|
+
activeCategory: string
|
|
42
|
+
totalCount: number
|
|
43
|
+
}) => {
|
|
44
|
+
const regularTotal =
|
|
45
|
+
activeCategory === "" ? Math.max(0, totalCount - FEATURED_COUNT) : totalCount
|
|
46
|
+
|
|
47
|
+
return Math.max(1, Math.ceil(regularTotal / PAGE_SIZE))
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const getCollectionWindow = ({
|
|
51
|
+
activeCategory,
|
|
52
|
+
currentPage,
|
|
53
|
+
}: {
|
|
54
|
+
activeCategory: string
|
|
55
|
+
currentPage: number
|
|
56
|
+
}) => {
|
|
57
|
+
const isFirstPageNoFilter = currentPage === 1 && activeCategory === ""
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
isFirstPageNoFilter,
|
|
61
|
+
fetchLimit: isFirstPageNoFilter ? PAGE_SIZE + FEATURED_COUNT : PAGE_SIZE,
|
|
62
|
+
fetchOffset: isFirstPageNoFilter
|
|
63
|
+
? 0
|
|
64
|
+
: (currentPage - 1) * PAGE_SIZE + (activeCategory === "" ? FEATURED_COUNT : 0),
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function CollectionToolbarSection({
|
|
69
|
+
activeCategory,
|
|
70
|
+
categories,
|
|
71
|
+
totalCount,
|
|
72
|
+
}: {
|
|
73
|
+
activeCategory: string
|
|
74
|
+
categories: ContentCategory[]
|
|
75
|
+
totalCount: number
|
|
76
|
+
}) {
|
|
77
|
+
return (
|
|
78
|
+
<ContentFilters
|
|
79
|
+
categories={categories}
|
|
80
|
+
activeCategory={activeCategory}
|
|
81
|
+
totalCount={totalCount}
|
|
82
|
+
filteredCount={totalCount}
|
|
83
|
+
/>
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function CollectionResultsSection({
|
|
88
|
+
activeCategory,
|
|
89
|
+
items,
|
|
90
|
+
isFirstPageNoFilter,
|
|
91
|
+
subtitle,
|
|
92
|
+
}: {
|
|
93
|
+
activeCategory: string
|
|
94
|
+
items: ContentItemCard[]
|
|
95
|
+
isFirstPageNoFilter: boolean
|
|
96
|
+
subtitle: string | null | undefined
|
|
97
|
+
}) {
|
|
98
|
+
const featuredItems = isFirstPageNoFilter ? items.slice(0, FEATURED_COUNT) : []
|
|
99
|
+
const regularItems = isFirstPageNoFilter ? items.slice(FEATURED_COUNT) : items
|
|
100
|
+
const isEmpty = featuredItems.length === 0 && regularItems.length === 0
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<>
|
|
104
|
+
{featuredItems.length > 0 ? (
|
|
105
|
+
<ContentGrid
|
|
106
|
+
items={featuredItems}
|
|
107
|
+
className="grid grid-cols-1 gap-8 md:grid-cols-2"
|
|
108
|
+
/>
|
|
109
|
+
) : null}
|
|
110
|
+
{isFirstPageNoFilter && activeCategory === "" && subtitle ? (
|
|
111
|
+
<h2 className="font-medium text-2xl leading-tight">{subtitle}</h2>
|
|
112
|
+
) : null}
|
|
113
|
+
{isEmpty ? (
|
|
114
|
+
<div className="rounded-[1.5rem] border border-black/15 border-dashed bg-black/[0.02] px-6 py-12 text-center">
|
|
115
|
+
<p className="font-medium text-lg">No content found</p>
|
|
116
|
+
<p className="mt-2 text-current/60 text-sm">
|
|
117
|
+
Try a different category to explore more content.
|
|
118
|
+
</p>
|
|
119
|
+
</div>
|
|
120
|
+
) : (
|
|
121
|
+
<ContentGrid items={regularItems} />
|
|
122
|
+
)}
|
|
123
|
+
</>
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function CollectionPaginationSection({
|
|
128
|
+
currentPage,
|
|
129
|
+
totalPages,
|
|
130
|
+
}: {
|
|
131
|
+
currentPage: number
|
|
132
|
+
totalPages: number
|
|
133
|
+
}) {
|
|
134
|
+
if (totalPages <= 1) return null
|
|
135
|
+
|
|
136
|
+
return (
|
|
137
|
+
<ContentPaginationNav currentPage={currentPage} totalPages={totalPages} />
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export async function ContentCollection({
|
|
142
|
+
block,
|
|
143
|
+
searchParams,
|
|
144
|
+
}: ContentCollectionProps) {
|
|
145
|
+
const resolvedParams = (await searchParams) ?? {}
|
|
146
|
+
const { currentPage, activeCategory } = getCollectionParams(resolvedParams)
|
|
147
|
+
|
|
148
|
+
const { data: categoriesData } = await fetchSanity<ContentCategory[]>({
|
|
149
|
+
query: ALL_BLOG_CATEGORIES_QUERY,
|
|
150
|
+
tags: ["blog-categories"],
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
const categories = categoriesData ?? []
|
|
154
|
+
const validCategorySlugs = new Set(
|
|
155
|
+
categories
|
|
156
|
+
.map((category) => category.slug?.current)
|
|
157
|
+
.filter((slug): slug is string => Boolean(slug))
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
const canonicalCategory =
|
|
161
|
+
activeCategory && validCategorySlugs.has(activeCategory) ? activeCategory : ""
|
|
162
|
+
|
|
163
|
+
const { data: totalCountData } = await fetchSanity<number>({
|
|
164
|
+
query: BLOG_ARTICLES_COUNT_QUERY,
|
|
165
|
+
params: { category: canonicalCategory },
|
|
166
|
+
tags: ["blog-articles-count"],
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
const totalCount = totalCountData ?? 0
|
|
170
|
+
const totalPages = getTotalPages({
|
|
171
|
+
activeCategory: canonicalCategory,
|
|
172
|
+
totalCount,
|
|
173
|
+
})
|
|
174
|
+
const canonicalPage = Math.min(currentPage, totalPages)
|
|
175
|
+
|
|
176
|
+
const { isFirstPageNoFilter, fetchLimit, fetchOffset } = getCollectionWindow({
|
|
177
|
+
activeCategory: canonicalCategory,
|
|
178
|
+
currentPage: canonicalPage,
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
const { data: itemsData } = await fetchSanity<ContentItemCard[]>({
|
|
182
|
+
query: BLOG_ARTICLES_PAGINATED_QUERY,
|
|
183
|
+
params: {
|
|
184
|
+
category: canonicalCategory,
|
|
185
|
+
offset: fetchOffset,
|
|
186
|
+
limit: fetchLimit,
|
|
187
|
+
},
|
|
188
|
+
tags: ["blog-articles"],
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
const items = itemsData ?? []
|
|
192
|
+
|
|
193
|
+
return (
|
|
194
|
+
<section className="flex flex-col gap-8 py-8">
|
|
195
|
+
<CollectionToolbarSection
|
|
196
|
+
activeCategory={canonicalCategory}
|
|
197
|
+
categories={categories}
|
|
198
|
+
totalCount={totalCount}
|
|
199
|
+
/>
|
|
200
|
+
<CollectionResultsSection
|
|
201
|
+
activeCategory={canonicalCategory}
|
|
202
|
+
items={items}
|
|
203
|
+
isFirstPageNoFilter={isFirstPageNoFilter}
|
|
204
|
+
subtitle={block.subtitle}
|
|
205
|
+
/>
|
|
206
|
+
<CollectionPaginationSection
|
|
207
|
+
currentPage={canonicalPage}
|
|
208
|
+
totalPages={totalPages}
|
|
209
|
+
/>
|
|
210
|
+
</section>
|
|
211
|
+
)
|
|
212
|
+
}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import type { PageBuilderBlock } from "@/components/page-builder/types"
|
|
2
2
|
import type { ALL_BLOG_ARTICLES_QUERY_RESULT } from "@/lib/integrations/sanity/sanity.types"
|
|
3
3
|
|
|
4
|
-
export type
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
>
|
|
4
|
+
export type SearchParams = Record<string, string | string[] | undefined>
|
|
5
|
+
|
|
6
|
+
export type ContentCollectionSearchParams = Promise<SearchParams> | undefined
|
|
8
7
|
|
|
9
8
|
export type ContentCollectionBlock = Extract<
|
|
10
9
|
PageBuilderBlock,
|
|
@@ -14,3 +13,5 @@ export type ContentCollectionBlock = Extract<
|
|
|
14
13
|
>
|
|
15
14
|
|
|
16
15
|
export type BlogArticleCard = ALL_BLOG_ARTICLES_QUERY_RESULT[number]
|
|
16
|
+
export type ContentCategory = NonNullable<BlogArticleCard["blogContent"]>["categories"][number]
|
|
17
|
+
export type ContentItemCard = BlogArticleCard
|
|
@@ -1,29 +1,37 @@
|
|
|
1
1
|
import { ArticleContent } from "./components/article-content"
|
|
2
|
+
import { ContentCollection } from "./components/content-collection"
|
|
2
3
|
import { Description } from "./components/description"
|
|
3
4
|
import { Hero } from "./components/hero"
|
|
4
|
-
import {
|
|
5
|
+
import type { ContentCollectionSearchParams } from "./components/content-collection/types"
|
|
5
6
|
import type { PageBuilderBlock, PageBuilderBlocks } from "./types"
|
|
6
7
|
|
|
7
8
|
type RendererProps = {
|
|
8
9
|
blocks: PageBuilderBlocks
|
|
10
|
+
searchParams: ContentCollectionSearchParams
|
|
9
11
|
}
|
|
10
12
|
|
|
11
|
-
export function Renderer({ blocks }: RendererProps) {
|
|
13
|
+
export function Renderer({ blocks, searchParams }: RendererProps) {
|
|
12
14
|
if (!blocks?.length) return null
|
|
13
15
|
|
|
14
16
|
return (
|
|
15
17
|
<div className="space-y-16">
|
|
16
18
|
{blocks.map((block: PageBuilderBlock) => (
|
|
17
|
-
<PageBuilderNode key={block._key} block={block} />
|
|
19
|
+
<PageBuilderNode key={block._key} block={block} searchParams={searchParams} />
|
|
18
20
|
))}
|
|
19
21
|
</div>
|
|
20
22
|
)
|
|
21
23
|
}
|
|
22
24
|
|
|
23
|
-
function PageBuilderNode({
|
|
25
|
+
function PageBuilderNode({
|
|
26
|
+
block,
|
|
27
|
+
searchParams,
|
|
28
|
+
}: {
|
|
29
|
+
block: PageBuilderBlock
|
|
30
|
+
searchParams: ContentCollectionSearchParams
|
|
31
|
+
}) {
|
|
24
32
|
switch (block._type) {
|
|
25
33
|
case "blogCollection":
|
|
26
|
-
return <ContentCollection block={block} />
|
|
34
|
+
return <ContentCollection block={block} searchParams={searchParams} />
|
|
27
35
|
case "blogContent":
|
|
28
36
|
return <ArticleContent block={block} />
|
|
29
37
|
case "hero":
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { stegaClean } from "@sanity/client/stega"
|
|
2
|
+
import type { ContentCollectionSearchParams } from "@/components/page-builder/components/content-collection/types"
|
|
2
3
|
import { Renderer } from "@/components/page-builder/renderer"
|
|
3
4
|
import type { PAGE_QUERY_RESULT } from "@/lib/integrations/sanity/sanity.types"
|
|
4
5
|
import { urlForImage } from "@/lib/integrations/sanity/utils/image"
|
|
@@ -11,11 +12,15 @@ import {
|
|
|
11
12
|
type PageDocumentProps = {
|
|
12
13
|
page: NonNullable<PAGE_QUERY_RESULT>
|
|
13
14
|
path: string | null
|
|
15
|
+
searchParams: ContentCollectionSearchParams
|
|
14
16
|
}
|
|
15
17
|
|
|
16
|
-
export const getPageSections = (
|
|
18
|
+
export const getPageSections = (
|
|
19
|
+
page: NonNullable<PAGE_QUERY_RESULT>,
|
|
20
|
+
searchParams: ContentCollectionSearchParams
|
|
21
|
+
) =>
|
|
17
22
|
page.pageBuilder?.length
|
|
18
|
-
? [<Renderer key="page-builder" blocks={page.pageBuilder} />]
|
|
23
|
+
? [<Renderer key="page-builder" blocks={page.pageBuilder} searchParams={searchParams} />]
|
|
19
24
|
: []
|
|
20
25
|
|
|
21
26
|
const buildBreadcrumbs = (
|
|
@@ -47,9 +52,9 @@ const buildBreadcrumbs = (
|
|
|
47
52
|
return items
|
|
48
53
|
}
|
|
49
54
|
|
|
50
|
-
export const PageDocument = ({ page, path }: PageDocumentProps) => {
|
|
55
|
+
export const PageDocument = ({ page, path, searchParams }: PageDocumentProps) => {
|
|
51
56
|
const title = stegaClean(page.title) || page.title
|
|
52
|
-
const sections = getPageSections(page)
|
|
57
|
+
const sections = getPageSections(page, searchParams)
|
|
53
58
|
|
|
54
59
|
const cleanTitle = stegaClean(page.title) || "Basement"
|
|
55
60
|
const cleanDescription = stegaClean(
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { draftMode } from "next/headers"
|
|
2
2
|
import { VisualEditing } from "next-sanity/visual-editing"
|
|
3
3
|
import { Suspense } from "react"
|
|
4
|
+
import { refreshAction } from "@/app/actions/refresh"
|
|
4
5
|
import { DraftModeToggle } from "@/components/sanity/draft-mode-toggle"
|
|
5
6
|
import { isSanityConfigured } from "@/lib/integrations/check-integration"
|
|
6
7
|
import { SanityLive } from "@/lib/integrations/sanity/live"
|
|
@@ -15,7 +16,7 @@ async function VisualEditingInner() {
|
|
|
15
16
|
<>
|
|
16
17
|
<DraftModeToggle />
|
|
17
18
|
<VisualEditing />
|
|
18
|
-
<SanityLive />
|
|
19
|
+
<SanityLive revalidateSyncTags={refreshAction} />
|
|
19
20
|
</>
|
|
20
21
|
)
|
|
21
22
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const HOMEPAGE_DOCUMENT_ID = "page-homepage"
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { fetchSanity } from "@/lib/integrations/sanity/live"
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
FOOTER_QUERY,
|
|
5
|
-
NAVBAR_QUERY,
|
|
3
|
+
SITE_LAYOUT_QUERY,
|
|
6
4
|
} from "@/lib/integrations/sanity/queries"
|
|
7
5
|
import type {
|
|
8
6
|
COMPANY_DATA_QUERY_RESULT,
|
|
@@ -14,22 +12,23 @@ export type NavbarData = NonNullable<NAVBAR_QUERY_RESULT>
|
|
|
14
12
|
export type FooterData = NonNullable<FOOTER_QUERY_RESULT>
|
|
15
13
|
export type CompanyData = NonNullable<COMPANY_DATA_QUERY_RESULT>
|
|
16
14
|
|
|
17
|
-
export
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
return data
|
|
15
|
+
export type SiteLayoutData = {
|
|
16
|
+
navbar: NavbarData | null
|
|
17
|
+
footer: FooterData | null
|
|
18
|
+
companyData: CompanyData | null
|
|
22
19
|
}
|
|
23
20
|
|
|
24
|
-
export const
|
|
25
|
-
const { data } = await sanityFetch({ query: FOOTER_QUERY, tags: ["footer"] })
|
|
26
|
-
return data
|
|
27
|
-
}
|
|
21
|
+
export const SANITY_LAYOUT_TAGS = ["navbar", "footer", "companyData"]
|
|
28
22
|
|
|
29
|
-
export const
|
|
30
|
-
const { data } = await
|
|
31
|
-
query:
|
|
32
|
-
tags:
|
|
23
|
+
export const getSiteLayoutData = async (): Promise<SiteLayoutData> => {
|
|
24
|
+
const { data } = await fetchSanity<SiteLayoutData>({
|
|
25
|
+
query: SITE_LAYOUT_QUERY,
|
|
26
|
+
tags: SANITY_LAYOUT_TAGS,
|
|
33
27
|
})
|
|
34
|
-
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
navbar: data?.navbar ?? null,
|
|
31
|
+
footer: data?.footer ?? null,
|
|
32
|
+
companyData: data?.companyData ?? null,
|
|
33
|
+
}
|
|
35
34
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { NextResponse } from "next/server"
|
|
2
|
+
import type { QueryParams } from "next-sanity"
|
|
2
3
|
import { defineLive } from "next-sanity/live"
|
|
3
4
|
import { isSanityConfigured } from "@/lib/integrations/check-integration"
|
|
4
5
|
import { client } from "../client"
|
|
@@ -10,8 +11,8 @@ const liveExports =
|
|
|
10
11
|
isConfigured && client
|
|
11
12
|
? defineLive({
|
|
12
13
|
client,
|
|
13
|
-
browserToken: sanityToken,
|
|
14
|
-
serverToken: sanityToken,
|
|
14
|
+
browserToken: sanityToken ?? false,
|
|
15
|
+
serverToken: sanityToken ?? false,
|
|
15
16
|
})
|
|
16
17
|
: null
|
|
17
18
|
|
|
@@ -20,6 +21,32 @@ export const sanityFetch =
|
|
|
20
21
|
|
|
21
22
|
export const SanityLive = liveExports?.SanityLive ?? (() => null)
|
|
22
23
|
|
|
24
|
+
type SanityFetchOptions = {
|
|
25
|
+
query: string
|
|
26
|
+
params?: QueryParams
|
|
27
|
+
tags?: string[]
|
|
28
|
+
stega?: boolean
|
|
29
|
+
perspective?: "published" | "drafts" | "previewDrafts"
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function fetchSanity<T = unknown>({
|
|
33
|
+
query,
|
|
34
|
+
params = {},
|
|
35
|
+
tags = [],
|
|
36
|
+
stega,
|
|
37
|
+
perspective,
|
|
38
|
+
}: SanityFetchOptions): Promise<{ data: T | null }> {
|
|
39
|
+
const { data } = await sanityFetch({
|
|
40
|
+
query,
|
|
41
|
+
params,
|
|
42
|
+
tags,
|
|
43
|
+
...(typeof stega === "boolean" ? { stega } : {}),
|
|
44
|
+
...(perspective ? { perspective } : {}),
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
return { data: (data as T | null) ?? null }
|
|
48
|
+
}
|
|
49
|
+
|
|
23
50
|
export const SanityFetch = async <T,>(
|
|
24
51
|
slug: string,
|
|
25
52
|
query: string,
|