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.
Files changed (70) hide show
  1. package/dist/helpers/integrate/merge-config.js +1 -1
  2. package/dist/helpers/integrate/merge-config.js.map +1 -1
  3. package/dist/helpers/integrate/merge-orchestrator.d.ts.map +1 -1
  4. package/dist/helpers/integrate/merge-orchestrator.js +5 -5
  5. package/dist/helpers/integrate/merge-orchestrator.js.map +1 -1
  6. package/dist/helpers/integrate/sanity/config.d.ts.map +1 -1
  7. package/dist/helpers/integrate/sanity/config.js +2 -1
  8. package/dist/helpers/integrate/sanity/config.js.map +1 -1
  9. package/dist/helpers/integrate/sanity/mergers/sitemap-merger.d.ts +1 -3
  10. package/dist/helpers/integrate/sanity/mergers/sitemap-merger.d.ts.map +1 -1
  11. package/dist/helpers/integrate/sanity/mergers/sitemap-merger.js +117 -76
  12. package/dist/helpers/integrate/sanity/mergers/sitemap-merger.js.map +1 -1
  13. package/package.json +1 -1
  14. package/src/helpers/integrate/sanity/files/app/api/blog/[slug]/route.ts +2 -1
  15. package/src/helpers/integrate/sanity/files/app/api/revalidate/route.ts +4 -1
  16. package/src/helpers/integrate/sanity/files/app/blog/[slug]/page.tsx +3 -1
  17. package/src/helpers/integrate/sanity/files/app/layout.tsx +2 -2
  18. package/src/helpers/integrate/sanity/files/app/sitemap.md/route.ts +29 -18
  19. package/src/helpers/integrate/sanity/files/lib/integrations/sanity/WEBHOOK-SETUP.md +74 -0
  20. package/src/helpers/integrate/sanity/files/lib/integrations/sanity/env.ts +4 -2
  21. package/src/helpers/integrate/sanity/files/lib/integrations/sanity/queries.ts +42 -0
  22. package/src/helpers/integrate/sanity/files/lib/integrations/sanity/sitemap.ts +90 -0
  23. package/src/helpers/integrate/sanity/files/lib/utils/metadata.ts +2 -2
  24. package/src/helpers/integrate/sanity/files/lib/utils/url.ts +23 -0
  25. package/src/templates/next-default/.env.example +3 -3
  26. package/src/templates/next-default/app/layout.tsx +2 -2
  27. package/src/templates/next-default/app/robots.ts +2 -2
  28. package/src/templates/next-default/app/sitemap.xml/route.ts +51 -0
  29. package/src/templates/next-default/lib/utils/metadata.ts +2 -2
  30. package/src/templates/next-default/lib/utils/url.ts +16 -0
  31. package/src/templates/next-experiments/.env.example +3 -3
  32. package/src/templates/next-experiments/app/layout.tsx +2 -2
  33. package/src/templates/next-experiments/app/robots.ts +0 -4
  34. package/src/templates/next-experiments/lib/utils/metadata.ts +2 -2
  35. package/src/templates/next-experiments/lib/utils/url.ts +16 -0
  36. package/src/templates/next-pagebuilder/app/(content)/[[...slug]]/page.tsx +17 -8
  37. package/src/templates/next-pagebuilder/app/(content)/layout.tsx +19 -7
  38. package/src/templates/next-pagebuilder/app/actions/refresh.ts +5 -0
  39. package/src/templates/next-pagebuilder/components/layout/footer/index.tsx +15 -19
  40. package/src/templates/next-pagebuilder/components/layout/header/index.tsx +3 -5
  41. package/src/templates/next-pagebuilder/components/layout/json-ld/index.tsx +11 -10
  42. package/src/templates/next-pagebuilder/components/layout/wrapper/index.tsx +14 -4
  43. package/src/templates/next-pagebuilder/components/page-builder/components/{post-collection → content-collection}/content-card.tsx +3 -5
  44. package/src/templates/next-pagebuilder/components/page-builder/components/content-collection/content-filters.tsx +93 -0
  45. package/src/templates/next-pagebuilder/components/page-builder/components/{post-collection → content-collection}/content-grid.tsx +7 -9
  46. package/src/templates/next-pagebuilder/components/page-builder/components/content-collection/content-pagination-nav.tsx +71 -0
  47. package/src/templates/next-pagebuilder/components/page-builder/components/content-collection/index.tsx +212 -0
  48. package/src/templates/next-pagebuilder/components/page-builder/components/{post-collection → content-collection}/types.ts +5 -4
  49. package/src/templates/next-pagebuilder/components/page-builder/renderer.tsx +13 -5
  50. package/src/templates/next-pagebuilder/components/page-document/index.tsx +9 -4
  51. package/src/templates/next-pagebuilder/components/sanity/visual-editing.tsx +2 -1
  52. package/src/templates/next-pagebuilder/lib/integrations/sanity/constants.ts +1 -0
  53. package/src/templates/next-pagebuilder/lib/integrations/sanity/fetchers/layout.ts +17 -18
  54. package/src/templates/next-pagebuilder/lib/integrations/sanity/live/index.tsx +29 -2
  55. package/src/templates/next-pagebuilder/lib/integrations/sanity/presentation.ts +118 -0
  56. package/src/templates/next-pagebuilder/lib/integrations/sanity/queries.ts +144 -31
  57. package/src/templates/next-pagebuilder/lib/integrations/sanity/sanity.config.ts +5 -100
  58. package/src/templates/next-pagebuilder/next.config.ts +3 -0
  59. package/src/templates/next-pagebuilder/package.json +1 -2
  60. package/src/templates/next-webgl/.env.example +3 -3
  61. package/src/templates/next-webgl/app/layout.tsx +2 -2
  62. package/src/templates/next-webgl/app/robots.ts +2 -2
  63. package/src/templates/next-webgl/app/sitemap.xml/route.ts +51 -0
  64. package/src/templates/next-webgl/lib/utils/metadata.ts +2 -2
  65. package/src/templates/next-webgl/lib/utils/url.ts +16 -0
  66. package/src/helpers/integrate/sanity/files/app/sitemap.ts +0 -61
  67. package/src/templates/next-default/app/sitemap.ts +0 -16
  68. package/src/templates/next-experiments/app/sitemap.ts +0 -16
  69. package/src/templates/next-pagebuilder/components/page-builder/components/post-collection/index.tsx +0 -28
  70. package/src/templates/next-webgl/app/sitemap.ts +0 -16
@@ -64,3 +64,45 @@ export const ARTICLE_BY_ID_QUERY = defineQuery(`
64
64
  _updatedAt
65
65
  }
66
66
  `);
67
+
68
+ export const PAGE_QUERY = defineQuery(`
69
+ *[_type == "example" && slug.current == $slug][0] {
70
+ _id,
71
+ title,
72
+ slug,
73
+ hero,
74
+ ${richTextWithLinks},
75
+ features,
76
+ tags,
77
+ metadata,
78
+ publishedAt,
79
+ _updatedAt
80
+ }
81
+ `);
82
+
83
+ export const SITEMAP_ARTICLES_QUERY = defineQuery(`
84
+ *[_type == "article" && defined(slug.current)] {
85
+ title,
86
+ slug,
87
+ _updatedAt,
88
+ "noIndex": metadata.noIndex
89
+ }
90
+ `);
91
+
92
+ export const SITEMAP_PAGES_QUERY = defineQuery(`
93
+ *[_type == "example" && defined(slug.current)] {
94
+ title,
95
+ slug,
96
+ _updatedAt,
97
+ "noIndex": metadata.noIndex
98
+ }
99
+ `);
100
+
101
+ export const ALL_PAGES_QUERY = defineQuery(`
102
+ *[_type == "example" && defined(slug.current)] | order(publishedAt desc) {
103
+ slug,
104
+ _updatedAt,
105
+ "noIndex": metadata.noIndex
106
+ }
107
+ `);
108
+
@@ -0,0 +1,90 @@
1
+ import { client } from "./client";
2
+ import { SITEMAP_ARTICLES_QUERY, SITEMAP_PAGES_QUERY } from "./queries";
3
+
4
+ export interface SitemapEntry {
5
+ loc: string;
6
+ lastmod: string;
7
+ changefreq: string;
8
+ priority: number;
9
+ }
10
+
11
+ interface SitemapDocument {
12
+ slug?: {
13
+ current?: string | null;
14
+ } | null;
15
+ _updatedAt: string;
16
+ noIndex?: boolean | null;
17
+ }
18
+
19
+ /**
20
+ * Fetch all published articles as sitemap entries.
21
+ * Excludes articles with metadata.noIndex set to true.
22
+ */
23
+ async function getArticleEntries(baseUrl: string): Promise<SitemapEntry[]> {
24
+ if (!client) return [];
25
+
26
+ try {
27
+ const articles =
28
+ (await client.fetch(SITEMAP_ARTICLES_QUERY)) as SitemapDocument[] | null;
29
+
30
+ if (!articles) return [];
31
+
32
+ return articles
33
+ .filter((article) => article.slug?.current && !article.noIndex)
34
+ .map((article) => ({
35
+ loc: `${baseUrl}/blog/${article.slug!.current!}`,
36
+ lastmod: new Date(article._updatedAt).toISOString(),
37
+ changefreq: "weekly",
38
+ priority: 0.7,
39
+ }));
40
+ } catch (error) {
41
+ console.error("Error fetching article sitemap entries:", error);
42
+ return [];
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Fetch all published CMS pages as sitemap entries.
48
+ * Supports nested slugs and excludes metadata.noIndex pages.
49
+ */
50
+ async function getPageEntries(baseUrl: string): Promise<SitemapEntry[]> {
51
+ if (!client) return [];
52
+
53
+ try {
54
+ const pages =
55
+ (await client.fetch(SITEMAP_PAGES_QUERY)) as SitemapDocument[] | null;
56
+
57
+ if (!pages) return [];
58
+
59
+ return pages
60
+ .filter((page) => page.slug?.current && !page.noIndex)
61
+ .map((page) => ({
62
+ loc: `${baseUrl}/${page.slug!.current!}`,
63
+ lastmod: new Date(page._updatedAt).toISOString(),
64
+ changefreq: "monthly",
65
+ priority: 0.8,
66
+ }));
67
+ } catch (error) {
68
+ console.error("Error fetching page sitemap entries:", error);
69
+ return [];
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Fetch all CMS-managed sitemap entries across all collections.
75
+ *
76
+ * To add a new collection, create a getter function (e.g. getPageEntries)
77
+ * and include it in the Promise.all below.
78
+ */
79
+ export async function getCMSSitemapEntries(
80
+ baseUrl: string,
81
+ ): Promise<SitemapEntry[]> {
82
+ const entries = await Promise.all([
83
+ getArticleEntries(baseUrl),
84
+ getPageEntries(baseUrl),
85
+ // Add more collections here as needed:
86
+ // getProjectEntries(baseUrl),
87
+ ]);
88
+
89
+ return entries.flat();
90
+ }
@@ -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,23 @@
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)
12
+ return process.env.NEXT_PUBLIC_BASE_URL;
13
+ if (process.env.VERCEL_PROJECT_PRODUCTION_URL)
14
+ return `https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}`;
15
+ if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`;
16
+ return "http://localhost:3000";
17
+ }
18
+
19
+ export function getPageUrl(baseUrl: string, slug: string | null): string {
20
+ if (!slug) return baseUrl;
21
+ const path = slug.startsWith("/") ? slug : `/${slug}`;
22
+ return `${baseUrl.replace(/\/$/, "")}${path}`;
23
+ }
@@ -13,9 +13,9 @@
13
13
  # CORE (Recommended)
14
14
  # ============================================
15
15
 
16
- # Base URL for your site (used for SEO, canonical URLs, etc.)
17
- # Development: http://localhost:3000
18
- # Production: https://your-domain.com
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
+ }
@@ -13,9 +13,9 @@
13
13
  # CORE (Recommended)
14
14
  # ============================================
15
15
 
16
- # Base URL for your site (used for SEO, canonical URLs, etc.)
17
- # Development: http://localhost:3000
18
- # Production: https://your-domain.com
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,8 +1,5 @@
1
1
  import type { MetadataRoute } from "next";
2
2
 
3
- const APP_BASE_URL =
4
- process.env.NEXT_PUBLIC_BASE_URL ?? "http://localhost:3000";
5
-
6
3
  export default function robots(): MetadataRoute.Robots {
7
4
  return {
8
5
  rules: {
@@ -10,6 +7,5 @@ export default function robots(): MetadataRoute.Robots {
10
7
  allow: "/",
11
8
  disallow: [],
12
9
  },
13
- sitemap: `${APP_BASE_URL}/sitemap.xml`,
14
10
  };
15
11
  }
@@ -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,6 +1,6 @@
1
1
  import { notFound } from "next/navigation"
2
2
  import { client } from "@/lib/integrations/sanity/client"
3
- import { sanityFetch } from "@/lib/integrations/sanity/live"
3
+ import { fetchSanity } from "@/lib/integrations/sanity/live"
4
4
  import {
5
5
  ALL_PAGE_SLUGS_QUERY,
6
6
  PAGE_QUERY,
@@ -9,12 +9,12 @@ import type {
9
9
  ALL_PAGE_SLUGS_QUERY_RESULT,
10
10
  PAGE_QUERY_RESULT,
11
11
  } from "@/lib/integrations/sanity/sanity.types"
12
+ import { PageDocument } from "@/components/page-document"
12
13
  import { generateSanityMetadata } from "@/lib/utils/metadata"
13
14
  import { getSlugTag } from "@/lib/utils/slug-tag"
14
- import { PageDocument } from "@/components/page-document"
15
15
 
16
16
  const getPageDocument = async (slug: string | null, stega = true) =>
17
- sanityFetch({
17
+ fetchSanity<PAGE_QUERY_RESULT>({
18
18
  query: PAGE_QUERY,
19
19
  params: { slug },
20
20
  tags: [getSlugTag(slug)],
@@ -32,15 +32,19 @@ const getResolvedPage = async (
32
32
  return (page as NonNullable<PAGE_QUERY_RESULT> | null) ?? null
33
33
  }
34
34
 
35
+ type SearchParams = Promise<{ [key: string]: string | string[] | undefined }>
36
+
35
37
  export const generateStaticParams = async () => {
36
- if (!client) return []
38
+ if (!client) return [{ slug: [] }]
37
39
 
38
40
  const slugs =
39
41
  await client.fetch<ALL_PAGE_SLUGS_QUERY_RESULT>(ALL_PAGE_SLUGS_QUERY)
40
42
 
43
+ if (!slugs?.length) return [{ slug: [] }]
44
+
41
45
  return (slugs ?? []).flatMap((page) => {
42
46
  if (!page.slug) return []
43
- return { slug: page.slug.split("/") }
47
+ return page.slug === "/" ? { slug: [] } : { slug: page.slug.split("/") }
44
48
  })
45
49
  }
46
50
 
@@ -56,13 +60,18 @@ export const generateMetadata = async ({ params }: Props) => {
56
60
  : {}
57
61
  }
58
62
 
59
- export default async function CatchAllPage({ params }: Props) {
63
+ export default async function CatchAllPage({
64
+ params,
65
+ searchParams,
66
+ }: Props & {
67
+ searchParams: SearchParams
68
+ }) {
60
69
  const { slug } = await params
61
70
 
62
71
  const path = toPath(slug)
63
72
  const page = await getResolvedPage(path)
64
73
 
65
- if (!page) return notFound()
74
+ if (!page) notFound()
66
75
 
67
- return <PageDocument page={page} path={path} />
76
+ return <PageDocument page={page} path={path} searchParams={searchParams} />
68
77
  }
@@ -1,13 +1,25 @@
1
1
  import { JsonLd } from "@/components/layout/json-ld"
2
2
  import { Wrapper } from "@/components/layout/wrapper"
3
3
  import { SanityVisualEditing } from "@/components/sanity/visual-editing"
4
+ import { getSiteLayoutData } from "@/lib/integrations/sanity/fetchers/layout"
4
5
 
5
- const Layout = async ({ children }: { children: React.ReactNode }) => (
6
- <>
7
- <JsonLd />
8
- <Wrapper>{children}</Wrapper>
9
- <SanityVisualEditing />
10
- </>
11
- )
6
+
7
+ const Layout = async ({ children }: { children: React.ReactNode }) => {
8
+ const layoutData = await getSiteLayoutData()
9
+
10
+ return (
11
+ <>
12
+ <JsonLd navbar={layoutData.navbar} companyData={layoutData.companyData} />
13
+ <Wrapper
14
+ navbar={layoutData.navbar}
15
+ footer={layoutData.footer}
16
+ companyData={layoutData.companyData}
17
+ >
18
+ {children}
19
+ </Wrapper>
20
+ <SanityVisualEditing />
21
+ </>
22
+ )
23
+ }
12
24
 
13
25
  export default Layout
@@ -0,0 +1,5 @@
1
+ "use client"
2
+
3
+ export async function refreshAction(): Promise<"refresh"> {
4
+ return "refresh"
5
+ }
@@ -3,30 +3,19 @@ import { SanityImage } from "@/components/ui/sanity-image"
3
3
  import {
4
4
  type CompanyData,
5
5
  type FooterData,
6
- getCompanyData,
7
- getFooter,
8
6
  } from "@/lib/integrations/sanity/fetchers/layout"
9
7
 
10
8
  type FooterLinkGroup = NonNullable<FooterData["links"]>[number]
11
9
  type FooterLinkItem = NonNullable<FooterLinkGroup["items"]>[number]
12
10
  type SocialLink = NonNullable<CompanyData["socialLinks"]>[number]
13
11
 
14
- export const Footer = async () => {
15
- const [footer, companyData] = await Promise.all([
16
- getFooter(),
17
- getCompanyData(),
18
- ])
19
-
20
- const year = new Date().getFullYear()
21
-
22
- if (!(footer || companyData)) {
23
- return (
24
- <footer className="border-black/10 border-t px-6 py-3">
25
- <p>&copy; {year} Basement</p>
26
- </footer>
27
- )
28
- }
29
-
12
+ export const Footer = ({
13
+ footer,
14
+ companyData,
15
+ }: {
16
+ footer: FooterData | null
17
+ companyData: CompanyData | null
18
+ }) => {
30
19
  const links = footer?.links ?? []
31
20
 
32
21
  return (
@@ -58,7 +47,9 @@ export const Footer = async () => {
58
47
  )}
59
48
 
60
49
  <div className="flex flex-row items-center justify-between gap-3 border-black/10 border-t pt-4">
61
- <p className="w-fit">&copy; {year} Basement</p>
50
+ <p className="w-fit">
51
+ &copy; <CurrentYear /> Basement
52
+ </p>
62
53
 
63
54
  {companyData?.socialLinks && companyData.socialLinks.length > 0 ? (
64
55
  <div className="flex w-fit flex-wrap items-center gap-x-4 gap-y-2">
@@ -93,3 +84,8 @@ export const Footer = async () => {
93
84
  </footer>
94
85
  )
95
86
  }
87
+
88
+
89
+ const CurrentYear = () => {
90
+ return <span>{new Date().getFullYear()}</span>
91
+ }
@@ -1,8 +1,6 @@
1
- import { getNavbar } from "@/lib/integrations/sanity/fetchers/layout"
2
-
3
1
  import { HeaderClient } from "./header-client"
2
+ import type { NavbarData } from "@/lib/integrations/sanity/fetchers/layout"
4
3
 
5
- export const Header = async () => {
6
- const navbar = await getNavbar()
7
- return <HeaderClient data={navbar} />
4
+ export const Header = ({ data }: { data: NavbarData | null }) => {
5
+ return <HeaderClient data={data} />
8
6
  }
@@ -1,7 +1,7 @@
1
1
  import { stegaClean } from "next-sanity"
2
- import {
3
- getCompanyData,
4
- getNavbar,
2
+ import type {
3
+ CompanyData,
4
+ NavbarData,
5
5
  } from "@/lib/integrations/sanity/fetchers/layout"
6
6
  import {
7
7
  generateOrganizationJsonLd,
@@ -12,17 +12,18 @@ import {
12
12
  const APP_DESCRIPTION =
13
13
  "Basement is the AI-native platform for audit and advisory firms. Automate engagement workflows using AI agents trusted by 50% of top 100 firms."
14
14
 
15
- export const JsonLd = async () => {
16
- const [navbarData, companyData] = await Promise.all([
17
- getNavbar(),
18
- getCompanyData(),
19
- ])
20
-
15
+ export const JsonLd = ({
16
+ navbar,
17
+ companyData,
18
+ }: {
19
+ navbar: NavbarData | null
20
+ companyData: CompanyData | null
21
+ }) => {
21
22
  const sameAs = companyData?.socialLinks
22
23
  ?.map((link) => stegaClean(link.url))
23
24
  .filter((url): url is string => Boolean(url))
24
25
 
25
- const logoUrl = navbarData?.logo?.asset?.url
26
+ const logoUrl = navbar?.logo?.asset?.url
26
27
 
27
28
  return (
28
29
  <>
@@ -1,9 +1,12 @@
1
1
  import Link from "next/link"
2
2
  import { Footer } from "@/components/layout/footer"
3
3
  import { Header } from "@/components/layout/header"
4
+ import type { SiteLayoutData } from "@/lib/integrations/sanity/fetchers/layout"
4
5
  import { cn } from "@/lib/styles/cn"
5
6
 
6
- interface WrapperProps extends React.HTMLAttributes<HTMLDivElement> {}
7
+ interface WrapperProps
8
+ extends React.HTMLAttributes<HTMLDivElement>,
9
+ SiteLayoutData {}
7
10
 
8
11
  const SkipToMainContent = () => (
9
12
  <Link
@@ -14,10 +17,17 @@ const SkipToMainContent = () => (
14
17
  </Link>
15
18
  )
16
19
 
17
- export const Wrapper = ({ children, className, ...props }: WrapperProps) => (
20
+ export const Wrapper = ({
21
+ children,
22
+ className,
23
+ navbar,
24
+ footer,
25
+ companyData,
26
+ ...props
27
+ }: WrapperProps) => (
18
28
  <>
19
29
  <SkipToMainContent />
20
- <Header />
30
+ <Header data={navbar} />
21
31
  <main
22
32
  id="main-content"
23
33
  className={cn("relative flex grow flex-col", className)}
@@ -25,6 +35,6 @@ export const Wrapper = ({ children, className, ...props }: WrapperProps) => (
25
35
  >
26
36
  {children}
27
37
  </main>
28
- <Footer />
38
+ <Footer footer={footer} companyData={companyData} />
29
39
  </>
30
40
  )
@@ -1,10 +1,10 @@
1
1
  import { Link } from "@/components/ui/link"
2
2
  import { SanityImage } from "@/components/ui/sanity-image"
3
3
  import { formatDate } from "@/lib/utils/format-date"
4
- import type { BlogArticleCard } from "./types"
4
+ import type { ContentItemCard } from "./types"
5
5
 
6
6
  type ContentCardProps = {
7
- article: BlogArticleCard
7
+ article: ContentItemCard
8
8
  className?: string | undefined
9
9
  }
10
10
 
@@ -46,9 +46,7 @@ export const ContentCard = ({ article, className }: ContentCardProps) => {
46
46
  </div>
47
47
  ) : null}
48
48
 
49
- <h3 className="text-balance font-medium text-lg leading-snug">
50
- {title}
51
- </h3>
49
+ <h3 className="text-balance font-medium text-lg leading-snug">{title}</h3>
52
50
 
53
51
  {authorName || formattedDate ? (
54
52
  <div className="flex items-center gap-2 text-current/60 text-sm">