bsmnt 0.2.11 → 0.3.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 (188) hide show
  1. package/dist/helpers/create/copy-template.d.ts +1 -1
  2. package/dist/helpers/create/copy-template.d.ts.map +1 -1
  3. package/dist/helpers/create/index.d.ts.map +1 -1
  4. package/dist/helpers/create/index.js +2 -1
  5. package/dist/helpers/create/index.js.map +1 -1
  6. package/dist/helpers/integrate/merge-config.d.ts.map +1 -1
  7. package/dist/helpers/integrate/merge-config.js +0 -2
  8. package/dist/helpers/integrate/merge-config.js.map +1 -1
  9. package/dist/helpers/integrate/sanity/config.d.ts.map +1 -1
  10. package/dist/helpers/integrate/sanity/config.js +2 -4
  11. package/dist/helpers/integrate/sanity/config.js.map +1 -1
  12. package/dist/index.js +84 -35
  13. package/dist/index.js.map +1 -1
  14. package/index.js +2 -2
  15. package/package.json +1 -1
  16. package/src/templates/next-default/.vscode/settings.json +1 -1
  17. package/src/templates/next-default/README.md +6 -7
  18. package/src/templates/next-default/app/layout.tsx +17 -4
  19. package/src/templates/next-default/biome.json +1 -1
  20. package/src/templates/next-default/css.d.ts +1 -0
  21. package/src/templates/next-default/lib/README.md +4 -8
  22. package/src/templates/next-default/lib/hooks/use-media.ts +3 -1
  23. package/src/templates/next-default/lib/styles/global.css +182 -0
  24. package/src/templates/next-default/lib/utils/json-ld.tsx +13 -18
  25. package/src/templates/next-default/lib/utils/portable-text-to-markdown.ts +83 -0
  26. package/src/templates/next-default/package.json +3 -3
  27. package/src/templates/next-experiments/.vscode/settings.json +1 -1
  28. package/src/templates/next-experiments/README.md +6 -7
  29. package/src/templates/next-experiments/app/layout.tsx +17 -4
  30. package/src/templates/next-experiments/biome.json +1 -1
  31. package/src/templates/next-experiments/css.d.ts +1 -0
  32. package/src/templates/next-experiments/lib/README.md +4 -8
  33. package/src/templates/next-experiments/lib/hooks/use-media.ts +3 -1
  34. package/src/templates/next-experiments/lib/styles/global.css +182 -0
  35. package/src/templates/next-experiments/lib/utils/json-ld.tsx +13 -18
  36. package/src/templates/next-experiments/lib/utils/portable-text-to-markdown.ts +83 -0
  37. package/src/templates/next-experiments/package.json +3 -3
  38. package/src/templates/next-pagebuilder/.env.example +11 -0
  39. package/src/templates/next-pagebuilder/README.md +23 -0
  40. package/src/templates/next-pagebuilder/_gitignore +67 -0
  41. package/src/templates/next-pagebuilder/app/(content)/[[...slug]]/page.tsx +68 -0
  42. package/src/templates/next-pagebuilder/app/(content)/layout.tsx +13 -0
  43. package/src/templates/next-pagebuilder/app/api/[[...slug]]/route.ts +100 -0
  44. package/src/templates/next-pagebuilder/app/api/draft-mode/disable/route.ts +7 -0
  45. package/src/templates/next-pagebuilder/app/api/draft-mode/enable/route.ts +20 -0
  46. package/src/templates/next-pagebuilder/app/api/revalidate/route.ts +121 -0
  47. package/src/templates/next-pagebuilder/app/favicon.ico +0 -0
  48. package/src/templates/next-pagebuilder/app/layout.tsx +80 -0
  49. package/src/templates/next-pagebuilder/app/robots.ts +15 -0
  50. package/src/templates/next-pagebuilder/app/sitemap.md/route.ts +124 -0
  51. package/src/templates/next-pagebuilder/app/sitemap.xml/route.ts +80 -0
  52. package/src/templates/next-pagebuilder/app/studio/[[...tool]]/page.tsx +8 -0
  53. package/src/templates/next-pagebuilder/biome.json +239 -0
  54. package/src/templates/next-pagebuilder/components/layout/footer/index.tsx +95 -0
  55. package/src/templates/next-pagebuilder/components/layout/header/components/cta-button.tsx +28 -0
  56. package/src/templates/next-pagebuilder/components/layout/header/components/mega-menu-panel.tsx +90 -0
  57. package/src/templates/next-pagebuilder/components/layout/header/components/nav-item-renderer.tsx +98 -0
  58. package/src/templates/next-pagebuilder/components/layout/header/components/nav-leaf-item.tsx +33 -0
  59. package/src/templates/next-pagebuilder/components/layout/header/components/types.ts +7 -0
  60. package/src/templates/next-pagebuilder/components/layout/header/header-client.tsx +110 -0
  61. package/src/templates/next-pagebuilder/components/layout/header/index.tsx +8 -0
  62. package/src/templates/next-pagebuilder/components/layout/json-ld/index.tsx +45 -0
  63. package/src/templates/next-pagebuilder/components/layout/wrapper/index.tsx +30 -0
  64. package/src/templates/next-pagebuilder/components/page-builder/components/article-content/index.tsx +83 -0
  65. package/src/templates/next-pagebuilder/components/page-builder/components/article-content/related-post-item.tsx +27 -0
  66. package/src/templates/next-pagebuilder/components/page-builder/components/description.tsx +17 -0
  67. package/src/templates/next-pagebuilder/components/page-builder/components/hero.tsx +17 -0
  68. package/src/templates/next-pagebuilder/components/page-builder/components/post-collection/content-card.tsx +66 -0
  69. package/src/templates/next-pagebuilder/components/page-builder/components/post-collection/content-grid.tsx +42 -0
  70. package/src/templates/next-pagebuilder/components/page-builder/components/post-collection/index.tsx +28 -0
  71. package/src/templates/next-pagebuilder/components/page-builder/components/post-collection/types.ts +16 -0
  72. package/src/templates/next-pagebuilder/components/page-builder/renderer.tsx +36 -0
  73. package/src/templates/next-pagebuilder/components/page-builder/types.ts +23 -0
  74. package/src/templates/next-pagebuilder/components/page-document/index.tsx +91 -0
  75. package/src/templates/next-pagebuilder/components/sanity/draft-mode-toggle.tsx +27 -0
  76. package/src/templates/next-pagebuilder/components/sanity/rich-text.tsx +87 -0
  77. package/src/templates/next-pagebuilder/components/sanity/visual-editing.tsx +27 -0
  78. package/src/templates/next-pagebuilder/components/ui/image/index.tsx +216 -0
  79. package/src/templates/next-pagebuilder/components/ui/link/index.tsx +152 -0
  80. package/src/templates/next-pagebuilder/components/ui/sanity-image/index.tsx +41 -0
  81. package/src/templates/next-pagebuilder/lib/integrations/check-integration.ts +5 -0
  82. package/src/templates/next-pagebuilder/lib/integrations/sanity/client.ts +27 -0
  83. package/src/templates/next-pagebuilder/lib/integrations/sanity/components/disable-draft-mode.tsx +23 -0
  84. package/src/templates/next-pagebuilder/lib/integrations/sanity/components/page-builder-input.tsx +36 -0
  85. package/src/templates/next-pagebuilder/lib/integrations/sanity/components/page-category-input.tsx +50 -0
  86. package/src/templates/next-pagebuilder/lib/integrations/sanity/components/rich-text.tsx +84 -0
  87. package/src/templates/next-pagebuilder/lib/integrations/sanity/confirm-publish-action.ts +40 -0
  88. package/src/templates/next-pagebuilder/lib/integrations/sanity/env.ts +34 -0
  89. package/src/templates/next-pagebuilder/lib/integrations/sanity/fetchers/layout.ts +35 -0
  90. package/src/templates/next-pagebuilder/lib/integrations/sanity/icons.ts +58 -0
  91. package/src/templates/next-pagebuilder/lib/integrations/sanity/live/index.tsx +61 -0
  92. package/src/templates/next-pagebuilder/lib/integrations/sanity/markdown-proxy.config.ts +50 -0
  93. package/src/templates/next-pagebuilder/lib/integrations/sanity/page-builder-config.ts +132 -0
  94. package/src/templates/next-pagebuilder/lib/integrations/sanity/page-category.ts +28 -0
  95. package/src/templates/next-pagebuilder/lib/integrations/sanity/queries.ts +281 -0
  96. package/src/templates/next-pagebuilder/lib/integrations/sanity/sanity.cli.ts +29 -0
  97. package/src/templates/next-pagebuilder/lib/integrations/sanity/sanity.config.ts +211 -0
  98. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/components/index.ts +4 -0
  99. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/components/reusable/blog-content.ts +89 -0
  100. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/components/reusable/description.ts +29 -0
  101. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/components/reusable/hero.ts +28 -0
  102. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/components/singleton/content-collection.ts +45 -0
  103. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/content/author.ts +70 -0
  104. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/content/blog-category.ts +55 -0
  105. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/index.ts +96 -0
  106. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/layout/company-data.ts +62 -0
  107. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/layout/footer.ts +79 -0
  108. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/layout/navbar.ts +74 -0
  109. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/link.ts +125 -0
  110. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/logo-field.ts +9 -0
  111. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/metadata.ts +68 -0
  112. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/nav-objects.ts +192 -0
  113. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/page-builder.ts +39 -0
  114. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/page-folder.ts +124 -0
  115. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/page.ts +232 -0
  116. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/richText.ts +63 -0
  117. package/src/templates/next-pagebuilder/lib/integrations/sanity/singletons.ts +44 -0
  118. package/src/templates/next-pagebuilder/lib/integrations/sanity/structure.ts +453 -0
  119. package/src/templates/next-pagebuilder/lib/integrations/sanity/utils/image.ts +8 -0
  120. package/src/templates/next-pagebuilder/lib/integrations/sanity/utils/link.ts +137 -0
  121. package/src/templates/next-pagebuilder/lib/integrations/sanity/utils/page-builder-markdown.ts +81 -0
  122. package/src/templates/next-pagebuilder/lib/scripts/sanity-typegen.ts +45 -0
  123. package/src/templates/next-pagebuilder/lib/styles/cn.ts +5 -0
  124. package/src/templates/next-pagebuilder/lib/styles/global.css +70 -0
  125. package/src/templates/next-pagebuilder/lib/utils/base-url.ts +17 -0
  126. package/src/templates/next-pagebuilder/lib/utils/format-date.ts +8 -0
  127. package/src/templates/next-pagebuilder/lib/utils/json-ld.tsx +213 -0
  128. package/src/templates/next-pagebuilder/lib/utils/metadata.ts +167 -0
  129. package/src/templates/next-pagebuilder/lib/utils/sitemap.ts +37 -0
  130. package/src/templates/next-pagebuilder/lib/utils/slug-tag.ts +6 -0
  131. package/src/templates/next-pagebuilder/next.config.ts +134 -0
  132. package/src/templates/next-pagebuilder/package.json +71 -0
  133. package/src/templates/next-pagebuilder/postcss.config.mjs +39 -0
  134. package/src/templates/next-pagebuilder/proxy.ts +81 -0
  135. package/src/templates/next-pagebuilder/svg.d.ts +5 -0
  136. package/src/templates/next-pagebuilder/tsconfig.json +38 -0
  137. package/src/templates/next-webgl/.vscode/settings.json +1 -1
  138. package/src/templates/next-webgl/README.md +6 -7
  139. package/src/templates/next-webgl/app/layout.tsx +17 -4
  140. package/src/templates/next-webgl/biome.json +1 -1
  141. package/src/templates/next-webgl/css.d.ts +1 -0
  142. package/src/templates/next-webgl/lib/README.md +4 -8
  143. package/src/templates/next-webgl/lib/hooks/use-media.ts +3 -1
  144. package/src/templates/next-webgl/lib/styles/global.css +182 -0
  145. package/src/templates/next-webgl/lib/utils/json-ld.tsx +13 -18
  146. package/src/templates/next-webgl/lib/utils/portable-text-to-markdown.ts +83 -0
  147. package/src/templates/next-webgl/package.json +3 -3
  148. package/src/helpers/integrate/sanity/files/lib/scripts/copy-sanity-mcp.ts +0 -23
  149. package/src/helpers/integrate/sanity/files/lib/scripts/generate-page.ts +0 -297
  150. package/src/templates/next-default/lib/scripts/dev.ts +0 -32
  151. package/src/templates/next-default/lib/styles/README.md +0 -13
  152. package/src/templates/next-default/lib/styles/fonts.ts +0 -20
  153. package/src/templates/next-default/lib/styles/index.css +0 -3
  154. package/src/templates/next-default/lib/styles/tokens.css +0 -179
  155. package/src/templates/next-default/lib/utils/README.md +0 -40
  156. package/src/templates/next-default/lib/utils/easings.ts +0 -240
  157. package/src/templates/next-default/lib/utils/fetch.ts +0 -84
  158. package/src/templates/next-default/lib/utils/global-css.d.ts +0 -1
  159. package/src/templates/next-default/lib/utils/math.ts +0 -236
  160. package/src/templates/next-default/lib/utils/strings.ts +0 -246
  161. package/src/templates/next-default/lib/utils/types.d.ts +0 -15
  162. package/src/templates/next-default/lib/utils/viewport.ts +0 -199
  163. package/src/templates/next-experiments/lib/scripts/dev.ts +0 -32
  164. package/src/templates/next-experiments/lib/styles/README.md +0 -13
  165. package/src/templates/next-experiments/lib/styles/fonts.ts +0 -20
  166. package/src/templates/next-experiments/lib/styles/index.css +0 -3
  167. package/src/templates/next-experiments/lib/styles/tokens.css +0 -179
  168. package/src/templates/next-experiments/lib/utils/README.md +0 -40
  169. package/src/templates/next-experiments/lib/utils/easings.ts +0 -240
  170. package/src/templates/next-experiments/lib/utils/fetch.ts +0 -84
  171. package/src/templates/next-experiments/lib/utils/global-css.d.ts +0 -1
  172. package/src/templates/next-experiments/lib/utils/math.ts +0 -236
  173. package/src/templates/next-experiments/lib/utils/strings.ts +0 -246
  174. package/src/templates/next-experiments/lib/utils/types.d.ts +0 -15
  175. package/src/templates/next-experiments/lib/utils/viewport.ts +0 -199
  176. package/src/templates/next-webgl/lib/scripts/dev.ts +0 -32
  177. package/src/templates/next-webgl/lib/styles/README.md +0 -13
  178. package/src/templates/next-webgl/lib/styles/fonts.ts +0 -20
  179. package/src/templates/next-webgl/lib/styles/index.css +0 -3
  180. package/src/templates/next-webgl/lib/styles/tokens.css +0 -179
  181. package/src/templates/next-webgl/lib/utils/README.md +0 -40
  182. package/src/templates/next-webgl/lib/utils/easings.ts +0 -240
  183. package/src/templates/next-webgl/lib/utils/fetch.ts +0 -84
  184. package/src/templates/next-webgl/lib/utils/global-css.d.ts +0 -1
  185. package/src/templates/next-webgl/lib/utils/math.ts +0 -236
  186. package/src/templates/next-webgl/lib/utils/strings.ts +0 -246
  187. package/src/templates/next-webgl/lib/utils/types.d.ts +0 -15
  188. package/src/templates/next-webgl/lib/utils/viewport.ts +0 -199
@@ -1,13 +1,8 @@
1
- import type {
2
- Article,
3
- BreadcrumbList,
4
- Organization,
5
- SearchAction,
6
- Thing,
7
- WebPage,
8
- WebSite,
9
- WithContext,
10
- } from "schema-dts";
1
+ type JsonLdValue = {
2
+ "@context": "https://schema.org";
3
+ "@type": string;
4
+ [key: string]: unknown;
5
+ };
11
6
 
12
7
  const APP_BASE_URL = process.env.NEXT_PUBLIC_BASE_URL;
13
8
 
@@ -25,10 +20,10 @@ function resolveUrl(value?: string) {
25
20
 
26
21
  /* -------------------------------- Component ------------------------------- */
27
22
 
28
- export function JsonLd<T extends Thing>({
23
+ export function JsonLd<T extends JsonLdValue>({
29
24
  data,
30
25
  }: {
31
- data: WithContext<T>;
26
+ data: T;
32
27
  }) {
33
28
  return (
34
29
  <script
@@ -52,7 +47,7 @@ interface WebSiteJsonLdOptions {
52
47
 
53
48
  export function generateWebSiteJsonLd(
54
49
  options: WebSiteJsonLdOptions
55
- ): WithContext<WebSite> {
50
+ ): JsonLdValue {
56
51
  const { name, url, description, searchUrl } = options;
57
52
  const resolvedUrl = resolveUrl(url);
58
53
  const resolvedSearchUrl = resolveUrl(searchUrl);
@@ -68,7 +63,7 @@ export function generateWebSiteJsonLd(
68
63
  "@type": "SearchAction",
69
64
  target: resolvedSearchUrl,
70
65
  "query-input": "required name=search_term_string",
71
- } as SearchAction & { "query-input": string },
66
+ },
72
67
  }),
73
68
  };
74
69
  }
@@ -83,7 +78,7 @@ interface OrganizationJsonLdOptions {
83
78
 
84
79
  export function generateOrganizationJsonLd(
85
80
  options: OrganizationJsonLdOptions
86
- ): WithContext<Organization> {
81
+ ): JsonLdValue {
87
82
  const { name, url, logo, description, sameAs } = options;
88
83
  const resolvedUrl = resolveUrl(url);
89
84
  const resolvedLogo = resolveUrl(logo);
@@ -110,7 +105,7 @@ interface WebPageJsonLdOptions {
110
105
 
111
106
  export function generateWebPageJsonLd(
112
107
  options: WebPageJsonLdOptions
113
- ): WithContext<WebPage> {
108
+ ): JsonLdValue {
114
109
  const { title, url, description, image, datePublished, dateModified } =
115
110
  options;
116
111
  const resolvedUrl = resolveUrl(url);
@@ -141,7 +136,7 @@ interface ArticleJsonLdOptions {
141
136
 
142
137
  export function generateArticleJsonLd(
143
138
  options: ArticleJsonLdOptions
144
- ): WithContext<Article> {
139
+ ): JsonLdValue {
145
140
  const {
146
141
  title,
147
142
  url,
@@ -181,7 +176,7 @@ interface BreadcrumbItem {
181
176
 
182
177
  export function generateBreadcrumbJsonLd(
183
178
  items: BreadcrumbItem[]
184
- ): WithContext<BreadcrumbList> {
179
+ ): JsonLdValue {
185
180
  return {
186
181
  "@context": "https://schema.org",
187
182
  "@type": "BreadcrumbList",
@@ -0,0 +1,83 @@
1
+ export interface PortableTextMarkDefinition {
2
+ _key?: string;
3
+ _type?: string;
4
+ href?: string;
5
+ }
6
+
7
+ export interface PortableTextSpan {
8
+ _type?: string;
9
+ text?: string;
10
+ marks?: string[];
11
+ }
12
+
13
+ export interface PortableTextBlock {
14
+ _type?: string;
15
+ style?: string;
16
+ listItem?: "bullet" | "number";
17
+ level?: number;
18
+ children?: PortableTextSpan[];
19
+ markDefs?: PortableTextMarkDefinition[];
20
+ }
21
+
22
+ const headingStyles: Record<string, string> = {
23
+ h1: "#",
24
+ h2: "##",
25
+ h3: "###",
26
+ h4: "####",
27
+ h5: "#####",
28
+ h6: "######",
29
+ }
30
+
31
+ function applyMarks(
32
+ text: string,
33
+ marks: string[] | undefined,
34
+ markDefs: PortableTextMarkDefinition[]
35
+ ) {
36
+ return (marks ?? []).reduce((result, mark) => {
37
+ if (mark === "strong") return `**${result}**`
38
+ if (mark === "em") return `*${result}*`
39
+ if (mark === "code") return `\`${result}\``
40
+
41
+ const link = markDefs.find((definition) => definition._key === mark)
42
+ if (link?._type === "link" && link.href) {
43
+ return `[${result}](${link.href})`
44
+ }
45
+
46
+ return result
47
+ }, text)
48
+ }
49
+
50
+ function serializeBlock(block: PortableTextBlock) {
51
+ const text = (block.children ?? [])
52
+ .map((child) =>
53
+ applyMarks(child.text ?? "", child.marks, block.markDefs ?? [])
54
+ )
55
+ .join("")
56
+ .trim()
57
+
58
+ if (!text) return ""
59
+
60
+ if (block.listItem) {
61
+ const level = Math.max((block.level ?? 1) - 1, 0)
62
+ const indent = " ".repeat(level)
63
+ const marker = block.listItem === "number" ? "1." : "-"
64
+ return `${indent}${marker} ${text}`
65
+ }
66
+
67
+ if (block.style === "blockquote") {
68
+ return `> ${text}`
69
+ }
70
+
71
+ const headingPrefix = headingStyles[block.style ?? ""]
72
+ if (headingPrefix) {
73
+ return `${headingPrefix} ${text}`
74
+ }
75
+
76
+ return text
77
+ }
78
+
79
+ export function portableTextToMarkdown(
80
+ value: PortableTextBlock[] | null | undefined
81
+ ) {
82
+ return (value ?? []).map(serializeBlock).filter(Boolean).join("\n\n")
83
+ }
@@ -8,9 +8,9 @@
8
8
  "analyze": "cross-env ANALYZE=true bun run build",
9
9
  "analyze:experimental": "next experimental-analyze",
10
10
  "build": "next build",
11
- "dev": "bun ./lib/scripts/dev.ts",
12
- "dev:https": "bun ./lib/scripts/dev.ts --https",
13
- "dev:inspect": "bun ./lib/scripts/dev.ts --inspect",
11
+ "dev": "next dev",
12
+ "dev:https": "next dev --experimental-https",
13
+ "dev:inspect": "next dev --inspect",
14
14
  "format": "biome format --write .",
15
15
  "lighthouse": "bunx @unlighthouse/cli --site http://localhost:3000",
16
16
  "lint": "biome lint --max-diagnostics=200",
@@ -0,0 +1,11 @@
1
+ # Base URL
2
+ NEXT_PUBLIC_BASE_URL=http://localhost:3000
3
+
4
+ # Draft Mode
5
+ DRAFT_MODE_TOKEN=
6
+
7
+ # Sanity CMS
8
+ NEXT_PUBLIC_SANITY_PROJECT_ID=
9
+ NEXT_PUBLIC_SANITY_DATASET=production
10
+ SANITY_API_READ_TOKEN=
11
+ SANITY_REVALIDATE_SECRET=
@@ -0,0 +1,23 @@
1
+ # Page Builder Template
2
+
3
+ Next.js + Sanity CMS page builder architecture. All URL structure is managed in Sanity, not in code.
4
+
5
+ ## Getting Started
6
+
7
+ ```bash
8
+ bun dev
9
+ ```
10
+
11
+ ## Architecture
12
+
13
+ - Single catch-all route `[[...slug]]` handles all pages
14
+ - Sanity page builder with composable content blocks
15
+ - Blog with categories, filtering, and pagination
16
+ - Visual editing and live preview support
17
+
18
+ ## Commands
19
+
20
+ - `bun dev` - Start dev server
21
+ - `bun build` - Production build
22
+ - `bun lint` - Run linter
23
+ - `bun sanity:typegen` - Generate Sanity types
@@ -0,0 +1,67 @@
1
+ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2
+
3
+ # dependencies
4
+ /node_modules
5
+ /.pnp
6
+ .pnp.*
7
+ .turbo
8
+ .npmrc
9
+ .yarn/*
10
+ !.yarn/patches
11
+ !.yarn/plugins
12
+ !.yarn/releases
13
+ !.yarn/versions
14
+
15
+ # testing
16
+ /coverage
17
+
18
+ # next.js
19
+ /.next/
20
+ /out/
21
+
22
+ # TypeScript
23
+ *.tsbuildinfo
24
+
25
+ # production
26
+ /build
27
+ /public/sw.js
28
+
29
+ # misc
30
+ .DS_Store
31
+ *.pem
32
+ .cursor/scratchpad.md
33
+
34
+ # debug
35
+ npm-debug.log*
36
+ yarn-debug.log*
37
+ yarn-error.log*
38
+
39
+ # local env files
40
+ .env
41
+ .env.local
42
+ .env*.local
43
+ .env.development.local
44
+ .env.test.local
45
+ .env.production.local
46
+
47
+ # vercel
48
+ .vercel
49
+
50
+ # tldr
51
+ .tldr
52
+
53
+ # eslint
54
+ .eslintcache
55
+
56
+ # certificates
57
+ certificates
58
+
59
+ next-env.d.ts
60
+ # claude
61
+ .claude
62
+
63
+ # unlighthouse
64
+ .unlighthouse/
65
+
66
+ # storybook
67
+ *storybook.log
@@ -0,0 +1,68 @@
1
+ import { notFound } from "next/navigation"
2
+ import { client } from "@/lib/integrations/sanity/client"
3
+ import { sanityFetch } from "@/lib/integrations/sanity/live"
4
+ import {
5
+ ALL_PAGE_SLUGS_QUERY,
6
+ PAGE_QUERY,
7
+ } from "@/lib/integrations/sanity/queries"
8
+ import type {
9
+ ALL_PAGE_SLUGS_QUERY_RESULT,
10
+ PAGE_QUERY_RESULT,
11
+ } from "@/lib/integrations/sanity/sanity.types"
12
+ import { generateSanityMetadata } from "@/lib/utils/metadata"
13
+ import { getSlugTag } from "@/lib/utils/slug-tag"
14
+ import { PageDocument } from "@/components/page-document"
15
+
16
+ const getPageDocument = async (slug: string | null, stega = true) =>
17
+ sanityFetch({
18
+ query: PAGE_QUERY,
19
+ params: { slug },
20
+ tags: [getSlugTag(slug)],
21
+ stega,
22
+ })
23
+
24
+ const toPath = (segments?: string[]) =>
25
+ segments?.filter(Boolean).join("/") || null
26
+
27
+ const getResolvedPage = async (
28
+ path: string | null,
29
+ stega = true
30
+ ): Promise<NonNullable<PAGE_QUERY_RESULT> | null> => {
31
+ const { data: page } = await getPageDocument(path, stega)
32
+ return (page as NonNullable<PAGE_QUERY_RESULT> | null) ?? null
33
+ }
34
+
35
+ export const generateStaticParams = async () => {
36
+ if (!client) return []
37
+
38
+ const slugs =
39
+ await client.fetch<ALL_PAGE_SLUGS_QUERY_RESULT>(ALL_PAGE_SLUGS_QUERY)
40
+
41
+ return (slugs ?? []).flatMap((page) => {
42
+ if (!page.slug) return []
43
+ return { slug: page.slug.split("/") }
44
+ })
45
+ }
46
+
47
+ type Props = { params: Promise<{ slug?: string[] }> }
48
+
49
+ export const generateMetadata = async ({ params }: Props) => {
50
+ const { slug } = await params
51
+ const path = toPath(slug)
52
+ const page = await getResolvedPage(path, false)
53
+
54
+ return page
55
+ ? generateSanityMetadata({ document: page, url: path ? `/${path}` : "/" })
56
+ : {}
57
+ }
58
+
59
+ export default async function CatchAllPage({ params }: Props) {
60
+ const { slug } = await params
61
+
62
+ const path = toPath(slug)
63
+ const page = await getResolvedPage(path)
64
+
65
+ if (!page) return notFound()
66
+
67
+ return <PageDocument page={page} path={path} />
68
+ }
@@ -0,0 +1,13 @@
1
+ import { JsonLd } from "@/components/layout/json-ld"
2
+ import { Wrapper } from "@/components/layout/wrapper"
3
+ import { SanityVisualEditing } from "@/components/sanity/visual-editing"
4
+
5
+ const Layout = async ({ children }: { children: React.ReactNode }) => (
6
+ <>
7
+ <JsonLd />
8
+ <Wrapper>{children}</Wrapper>
9
+ <SanityVisualEditing />
10
+ </>
11
+ )
12
+
13
+ export default Layout
@@ -0,0 +1,100 @@
1
+ import { NextResponse } from "next/server"
2
+ import { isSanityConfigured } from "@/lib/integrations/check-integration"
3
+ import { sanityFetch } from "@/lib/integrations/sanity/live"
4
+ import { PAGE_QUERY } from "@/lib/integrations/sanity/queries"
5
+ import type { PAGE_QUERY_RESULT } from "@/lib/integrations/sanity/sanity.types"
6
+ import { renderPageBuilderMarkdown } from "@/lib/integrations/sanity/utils/page-builder-markdown"
7
+ import { getBaseUrl } from "@/lib/utils/base-url"
8
+
9
+ type PageResult = NonNullable<PAGE_QUERY_RESULT>
10
+
11
+ const getSlugPath = (slugParts?: string[]) => {
12
+ if (!slugParts?.length) return null
13
+
14
+ const lastSegment = slugParts.at(-1)
15
+
16
+ if (!lastSegment?.endsWith(".md")) return null
17
+
18
+ const pathSegments = [...slugParts]
19
+ pathSegments[pathSegments.length - 1] = lastSegment.slice(0, -3)
20
+
21
+ const slug = pathSegments.filter(Boolean).join("/")
22
+ if (!slug) return null
23
+
24
+ return slug === "index" ? null : slug
25
+ }
26
+
27
+ export async function GET(
28
+ _request: Request,
29
+ props: { params: Promise<{ slug?: string[] }> }
30
+ ) {
31
+ const { slug: rawSlugParts } = await props.params
32
+ const slug = getSlugPath(rawSlugParts)
33
+
34
+ if (
35
+ rawSlugParts?.length &&
36
+ slug === null &&
37
+ rawSlugParts.join("/") !== "index.md"
38
+ ) {
39
+ return new NextResponse(null, { status: 404 })
40
+ }
41
+
42
+ if (!rawSlugParts?.length) {
43
+ return new NextResponse(null, { status: 404 })
44
+ }
45
+
46
+ if (!isSanityConfigured()) {
47
+ return new NextResponse("# 404 Not Found\n\nCMS not configured.", {
48
+ status: 404,
49
+ headers: { "Content-Type": "text/markdown; charset=utf-8" },
50
+ })
51
+ }
52
+
53
+ const { data } = await sanityFetch({
54
+ query: PAGE_QUERY,
55
+ params: { slug },
56
+ })
57
+
58
+ const page = data as PageResult | null
59
+
60
+ const baseUrl = getBaseUrl()
61
+
62
+ if (!page) {
63
+ return new NextResponse("# 404 Not Found\n\nPage not found.", {
64
+ status: 404,
65
+ headers: { "Content-Type": "text/markdown; charset=utf-8" },
66
+ })
67
+ }
68
+
69
+ const body = renderPageBuilderMarkdown(page.pageBuilder)
70
+ const canonicalPath = slug ? `/${slug}` : "/"
71
+
72
+ return new NextResponse(
73
+ [
74
+ `# ${page.title}`,
75
+ "",
76
+ page._updatedAt
77
+ ? `**Updated:** ${new Date(page._updatedAt).toLocaleDateString()}`
78
+ : null,
79
+ "",
80
+ `Canonical URL: ${baseUrl}${canonicalPath}`,
81
+ "",
82
+ "---",
83
+ "",
84
+ body || "No page builder content yet.",
85
+ "",
86
+ "---",
87
+ "",
88
+ `[View all content](${baseUrl}/sitemap.md)`,
89
+ ]
90
+ .filter((part): part is string => part !== null)
91
+ .join("\n"),
92
+ {
93
+ headers: {
94
+ "Content-Type": "text/markdown; charset=utf-8",
95
+ Vary: "Accept",
96
+ "X-Content-Type-Options": "nosniff",
97
+ },
98
+ }
99
+ )
100
+ }
@@ -0,0 +1,7 @@
1
+ import { draftMode } from "next/headers"
2
+ import { type NextRequest, NextResponse } from "next/server"
3
+
4
+ export async function GET(request: NextRequest) {
5
+ ;(await draftMode()).disable()
6
+ return NextResponse.redirect(new URL("/", request.url))
7
+ }
@@ -0,0 +1,20 @@
1
+ import { NextResponse } from "next/server"
2
+ import { defineEnableDraftMode } from "next-sanity/draft-mode"
3
+ import { isSanityConfigured } from "@/lib/integrations/check-integration"
4
+ import { client } from "@/lib/integrations/sanity/client"
5
+ import { sanityToken } from "@/lib/integrations/sanity/env"
6
+
7
+ const draftModeHandler =
8
+ isSanityConfigured() && client
9
+ ? defineEnableDraftMode({
10
+ client: client.withConfig({ token: sanityToken }),
11
+ })
12
+ : {
13
+ GET: () =>
14
+ NextResponse.json(
15
+ { error: "Sanity is not configured" },
16
+ { status: 503 }
17
+ ),
18
+ }
19
+
20
+ export const { GET } = draftModeHandler
@@ -0,0 +1,121 @@
1
+ import { revalidatePath, revalidateTag } from "next/cache"
2
+ import { type NextRequest, NextResponse } from "next/server"
3
+ import { parseBody } from "next-sanity/webhook"
4
+ import { client } from "@/lib/integrations/sanity/client"
5
+ import { SANITY_LAYOUT_TAGS } from "@/lib/integrations/sanity/fetchers/layout"
6
+ import { pageBuilderReferenceMembers as pbrm } from "@/lib/integrations/sanity/page-builder-config"
7
+ import {
8
+ P_REF_PB_COMP_QUERY,
9
+ P_REV_SLUGS_QUERY,
10
+ } from "@/lib/integrations/sanity/queries"
11
+ import type { P_REF_PB_COMP_QUERY_RESULT } from "@/lib/integrations/sanity/sanity.types"
12
+ import { getPagePath } from "@/lib/utils/sitemap"
13
+
14
+ const ERRORS = {
15
+ BAD_REQ: ["Bad Request", { status: 400 }],
16
+ INVALID_SIG: ["Invalid signature", { status: 401 }],
17
+ } as const
18
+
19
+ type WebhookSlug = {
20
+ current?: string | null
21
+ }
22
+
23
+ type WebhookDocument = {
24
+ _id: string
25
+ _type: string
26
+ resolvedSlug?: string | null
27
+ slug?: WebhookSlug | null
28
+ }
29
+
30
+ type MutationOperation = "create" | "update" | "delete"
31
+
32
+ type RevalidateWebhookBody = WebhookDocument & {
33
+ operation?: MutationOperation
34
+ before?: Partial<WebhookDocument> | null
35
+ after?: Partial<WebhookDocument> | null
36
+ }
37
+
38
+ const pageBuilderComponent = new Set<string>(pbrm.map((m) => m.documentType))
39
+
40
+ const normalizeRevalidationSlug = (slug: string | null | undefined) => {
41
+ if (slug === undefined) return null
42
+ if (slug === null) return "/"
43
+ return `/${slug}`
44
+ }
45
+
46
+ const getCanonicalPageSlug = async (documentId: string) => {
47
+ if (!client) return null
48
+
49
+ const pageSlugs = await client.fetch<Array<{ slug: string | null }>>(
50
+ P_REV_SLUGS_QUERY,
51
+ { documentIds: [documentId] }
52
+ )
53
+
54
+ return normalizeRevalidationSlug(
55
+ pageSlugs.find((page) => page.slug !== undefined)?.slug
56
+ )
57
+ }
58
+
59
+ const getPagesThatReferenceComp = async (documentId: string) => {
60
+ if (!client) return []
61
+
62
+ const pages = await client.fetch<P_REF_PB_COMP_QUERY_RESULT>(
63
+ P_REF_PB_COMP_QUERY,
64
+ {
65
+ documentId,
66
+ }
67
+ )
68
+
69
+ return (pages ?? []).map((page) => normalizeRevalidationSlug(page.slug))
70
+ }
71
+
72
+ const getAffectedPageSlugsForWebhook = async (body: RevalidateWebhookBody) => {
73
+ const pages: Array<string | null> = []
74
+
75
+ if (body.operation === "delete" && body?.before?.slug?.current) {
76
+ revalidateTag(body.before.slug.current, "max")
77
+ }
78
+
79
+ // If the document is a page, get its slug
80
+ if (body._type === "page") {
81
+ const data = await getCanonicalPageSlug(body._id)
82
+ if (data !== null) pages.push(data)
83
+ }
84
+
85
+ // If the document is a page builder component, get the page slugs that reference it
86
+ if (pageBuilderComponent.has(body._type)) {
87
+ const data = await getPagesThatReferenceComp(body._id)
88
+ pages.push(...data)
89
+ }
90
+
91
+ return pages.filter((page): page is string => page !== null)
92
+ }
93
+
94
+ export async function POST(request: NextRequest) {
95
+ try {
96
+ const { body, isValidSignature } = await parseBody<RevalidateWebhookBody>(
97
+ request,
98
+ process.env.SANITY_REVALIDATE_SECRET
99
+ )
100
+
101
+ if (!isValidSignature) return new Response(...ERRORS.INVALID_SIG)
102
+ if (!(body?._type && body._id)) return new Response(...ERRORS.BAD_REQ)
103
+ if (body._id.startsWith("drafts."))
104
+ return NextResponse.json({ status: 200, now: Date.now(), skipped: true })
105
+
106
+ const affectedPageSlugs = await getAffectedPageSlugsForWebhook(body)
107
+
108
+ for (const slug of affectedPageSlugs) {
109
+ revalidatePath(getPagePath(slug))
110
+ }
111
+
112
+ if (SANITY_LAYOUT_TAGS.includes(body?._type)) {
113
+ revalidateTag(body._type, "max")
114
+ }
115
+
116
+ return NextResponse.json({ status: 200, now: Date.now() })
117
+ } catch (error) {
118
+ console.error("Revalidation error:", error)
119
+ return new Response("Internal Server Error", { status: 500 })
120
+ }
121
+ }