bsmnt 0.9.0 → 0.11.0

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 (344) hide show
  1. package/dist/application/add-integration/index.d.ts.map +1 -1
  2. package/dist/application/add-integration/index.js +19 -2
  3. package/dist/application/add-integration/index.js.map +1 -1
  4. package/dist/application/shared/node-version.js.map +1 -1
  5. package/dist/core/create/internal-ids.d.ts +3 -0
  6. package/dist/core/create/internal-ids.d.ts.map +1 -1
  7. package/dist/core/create/internal-ids.js +4 -0
  8. package/dist/core/create/internal-ids.js.map +1 -1
  9. package/dist/domain/agents.d.ts.map +1 -1
  10. package/dist/domain/animations.d.ts.map +1 -1
  11. package/dist/domain/cms.d.ts.map +1 -1
  12. package/dist/domain/forms.d.ts.map +1 -1
  13. package/dist/domain/hooks.d.ts.map +1 -1
  14. package/dist/domain/skills.d.ts.map +1 -1
  15. package/dist/domain/templates.d.ts +10 -0
  16. package/dist/domain/templates.d.ts.map +1 -1
  17. package/dist/domain/templates.js +5 -0
  18. package/dist/domain/templates.js.map +1 -1
  19. package/dist/index.js +18 -5
  20. package/dist/index.js.map +1 -1
  21. package/dist/infrastructure/agents/register-sanity-mcp.js +3 -8
  22. package/dist/infrastructure/agents/register-sanity-mcp.js.map +1 -1
  23. package/dist/infrastructure/agents/register-storybook-mcp.d.ts +18 -0
  24. package/dist/infrastructure/agents/register-storybook-mcp.d.ts.map +1 -0
  25. package/dist/infrastructure/agents/register-storybook-mcp.js +157 -0
  26. package/dist/infrastructure/agents/register-storybook-mcp.js.map +1 -0
  27. package/dist/infrastructure/create/lifecycle-registry.d.ts.map +1 -1
  28. package/dist/infrastructure/create/lifecycle-registry.js +39 -1
  29. package/dist/infrastructure/create/lifecycle-registry.js.map +1 -1
  30. package/dist/infrastructure/create/merge-registry.d.ts.map +1 -1
  31. package/dist/infrastructure/create/merge-registry.js +4 -0
  32. package/dist/infrastructure/create/merge-registry.js.map +1 -1
  33. package/dist/infrastructure/create/sanity-auth.js.map +1 -1
  34. package/dist/infrastructure/create/setup-agent.d.ts +6 -1
  35. package/dist/infrastructure/create/setup-agent.d.ts.map +1 -1
  36. package/dist/infrastructure/create/setup-agent.js +23 -21
  37. package/dist/infrastructure/create/setup-agent.js.map +1 -1
  38. package/dist/infrastructure/create/setup-dotenvx.js.map +1 -1
  39. package/dist/infrastructure/create/setup-remote.js.map +1 -1
  40. package/dist/infrastructure/create/setup-sanity.d.ts.map +1 -1
  41. package/dist/infrastructure/create/setup-sanity.js +12 -1
  42. package/dist/infrastructure/create/setup-sanity.js.map +1 -1
  43. package/dist/infrastructure/integrations/ensure-studio-headers.d.ts +6 -0
  44. package/dist/infrastructure/integrations/ensure-studio-headers.d.ts.map +1 -0
  45. package/dist/infrastructure/integrations/ensure-studio-headers.js +153 -0
  46. package/dist/infrastructure/integrations/ensure-studio-headers.js.map +1 -0
  47. package/dist/infrastructure/integrations/inject-integration.d.ts.map +1 -1
  48. package/dist/infrastructure/integrations/inject-integration.js +2 -0
  49. package/dist/infrastructure/integrations/inject-integration.js.map +1 -1
  50. package/dist/infrastructure/update/notifier.js.map +1 -1
  51. package/dist/modules/features/cms/index.d.ts.map +1 -1
  52. package/dist/modules/features/cms/index.js +2 -0
  53. package/dist/modules/features/cms/index.js.map +1 -1
  54. package/dist/modules/features/cms/sanity/config.d.ts.map +1 -1
  55. package/dist/modules/features/cms/sanity/config.js +3 -1
  56. package/dist/modules/features/cms/sanity/config.js.map +1 -1
  57. package/dist/modules/features/cms/sanity/index.d.ts.map +1 -1
  58. package/dist/modules/features/cms/sanity/index.js +10 -1
  59. package/dist/modules/features/cms/sanity/index.js.map +1 -1
  60. package/dist/modules/features/cms/sanity/mergers/check-integration.d.ts.map +1 -1
  61. package/dist/modules/features/cms/sanity/mergers/check-integration.js +15 -6
  62. package/dist/modules/features/cms/sanity/mergers/check-integration.js.map +1 -1
  63. package/dist/modules/features/cms/sanity/mergers/knip.d.ts.map +1 -1
  64. package/dist/modules/features/cms/sanity/mergers/knip.js +1 -0
  65. package/dist/modules/features/cms/sanity/mergers/knip.js.map +1 -1
  66. package/dist/modules/features/cms/sanity/mergers/layout.js.map +1 -1
  67. package/dist/modules/features/cms/sanity/mergers/package-json.d.ts +12 -0
  68. package/dist/modules/features/cms/sanity/mergers/package-json.d.ts.map +1 -0
  69. package/dist/modules/features/cms/sanity/mergers/package-json.js +19 -0
  70. package/dist/modules/features/cms/sanity/mergers/package-json.js.map +1 -0
  71. package/dist/modules/features/cms/sanity-pagebuilder/config.d.ts.map +1 -1
  72. package/dist/modules/features/cms/sanity-pagebuilder/config.js +2 -1
  73. package/dist/modules/features/cms/sanity-pagebuilder/config.js.map +1 -1
  74. package/dist/modules/features/cms/sanity-pagebuilder/index.d.ts.map +1 -1
  75. package/dist/modules/features/cms/sanity-pagebuilder/index.js +7 -1
  76. package/dist/modules/features/cms/sanity-pagebuilder/index.js.map +1 -1
  77. package/dist/modules/features/cms/sanity-pagebuilder-cache/config.d.ts +14 -0
  78. package/dist/modules/features/cms/sanity-pagebuilder-cache/config.d.ts.map +1 -0
  79. package/dist/modules/features/cms/sanity-pagebuilder-cache/config.js +50 -0
  80. package/dist/modules/features/cms/sanity-pagebuilder-cache/config.js.map +1 -0
  81. package/dist/modules/features/cms/sanity-pagebuilder-cache/index.d.ts +3 -0
  82. package/dist/modules/features/cms/sanity-pagebuilder-cache/index.d.ts.map +1 -0
  83. package/dist/modules/features/cms/sanity-pagebuilder-cache/index.js +55 -0
  84. package/dist/modules/features/cms/sanity-pagebuilder-cache/index.js.map +1 -0
  85. package/dist/modules/presets/index.d.ts.map +1 -1
  86. package/dist/modules/presets/index.js +2 -0
  87. package/dist/modules/presets/index.js.map +1 -1
  88. package/dist/modules/presets/next-pagebuilder-cache.d.ts +3 -0
  89. package/dist/modules/presets/next-pagebuilder-cache.d.ts.map +1 -0
  90. package/dist/modules/presets/next-pagebuilder-cache.js +10 -0
  91. package/dist/modules/presets/next-pagebuilder-cache.js.map +1 -0
  92. package/dist/paths.d.ts +6 -0
  93. package/dist/paths.d.ts.map +1 -1
  94. package/dist/paths.js +6 -0
  95. package/dist/paths.js.map +1 -1
  96. package/package.json +6 -3
  97. package/src/modules/features/cms/sanity/files/app/api/blog/[slug]/route.ts +4 -1
  98. package/src/modules/features/cms/sanity/files/app/sitemap.md/route.ts +2 -1
  99. package/src/modules/features/cms/sanity/files/components/ui/sanity-image/index.tsx +19 -5
  100. package/src/modules/features/cms/sanity/files/lib/integrations/check-integration.ts +7 -6
  101. package/src/modules/features/cms/sanity/files/lib/integrations/sanity/README.md +32 -1
  102. package/src/modules/features/cms/sanity/files/lib/integrations/sanity/components/rich-text.tsx +1 -0
  103. package/src/modules/features/cms/sanity/files/lib/integrations/sanity/env.ts +2 -2
  104. package/src/modules/features/cms/sanity/files/proxy.ts +9 -0
  105. package/src/modules/features/cms/sanity-pagebuilder/files/app/api/[[...slug]]/route.ts +12 -3
  106. package/src/modules/features/cms/sanity-pagebuilder/files/app/api/revalidate/route.ts +172 -24
  107. package/src/modules/features/cms/sanity-pagebuilder/files/app/feed.xml/route.ts +14 -7
  108. package/src/modules/features/cms/sanity-pagebuilder/files/app/sitemap.md/route.ts +8 -2
  109. package/src/modules/features/cms/sanity-pagebuilder/files/app/studio/[[...tool]]/page.tsx +16 -1
  110. package/src/modules/features/cms/sanity-pagebuilder/files/components/sanity/rich-text.tsx +1 -0
  111. package/src/modules/features/cms/sanity-pagebuilder/files/components/sanity/visual-editing.tsx +2 -2
  112. package/src/modules/features/cms/sanity-pagebuilder/files/components/ui/sanity-image/index.tsx +15 -5
  113. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/check-integration.ts +6 -5
  114. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/client.ts +8 -0
  115. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/slug-field.tsx +13 -7
  116. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/slug-notices.tsx +125 -0
  117. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/env.ts +2 -2
  118. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/fetchers/layout.ts +22 -52
  119. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/icons.ts +2 -2
  120. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/live/index.tsx +4 -2
  121. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/markdown-proxy.config.ts +1 -1
  122. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/page-builder-config.ts +7 -0
  123. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/page-tree-pane.tsx +70 -57
  124. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/queries.ts +119 -29
  125. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/revalidate-paths.ts +70 -0
  126. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/index.ts +1 -0
  127. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/reusable/rich-text-block.ts +39 -0
  128. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/index.ts +2 -0
  129. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/layout/redirect.ts +35 -4
  130. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/page.ts +12 -0
  131. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/tags.ts +56 -0
  132. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/utils/page-builder-markdown.ts +12 -0
  133. package/src/modules/features/cms/sanity-pagebuilder/files/lib/utils/sitemap.ts +11 -8
  134. package/src/modules/features/cms/sanity-pagebuilder/files/proxy.ts +9 -0
  135. package/src/modules/features/cms/sanity-pagebuilder-cache/files/.knip.json +8 -0
  136. package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/api/[[...slug]]/route.ts +112 -0
  137. package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/api/draft-mode/disable/route.ts +7 -0
  138. package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/api/draft-mode/enable/route.ts +20 -0
  139. package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/api/revalidate/route.ts +292 -0
  140. package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/feed.xml/route.ts +66 -0
  141. package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/sitemap.md/route.ts +132 -0
  142. package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/studio/[[...tool]]/layout.tsx +26 -0
  143. package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/studio/[[...tool]]/page.tsx +23 -0
  144. package/src/modules/features/cms/sanity-pagebuilder-cache/files/components/sanity/draft-mode-toggle.tsx +27 -0
  145. package/src/modules/features/cms/sanity-pagebuilder-cache/files/components/sanity/rich-text.tsx +88 -0
  146. package/src/modules/features/cms/sanity-pagebuilder-cache/files/components/sanity/visual-editing.tsx +11 -0
  147. package/src/modules/features/cms/sanity-pagebuilder-cache/files/components/ui/sanity-image/index.tsx +51 -0
  148. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/check-integration.ts +6 -0
  149. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/client.ts +37 -0
  150. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/block-actions-item.tsx +81 -0
  151. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/disable-draft-mode.tsx +23 -0
  152. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/page-builder-input.tsx +39 -0
  153. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/page-category-input.tsx +50 -0
  154. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/same-as-input.tsx +66 -0
  155. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/slug-field.tsx +24 -0
  156. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/slug-input.tsx +14 -0
  157. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/slug-notices.tsx +125 -0
  158. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/confirm-publish-action.ts +40 -0
  159. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/env.ts +34 -0
  160. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/fetchers/layout.ts +39 -0
  161. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/fetchers/redirects.ts +22 -0
  162. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/icons.ts +81 -0
  163. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/live/index.tsx +140 -0
  164. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/markdown-proxy.config.ts +50 -0
  165. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/page-builder-config.ts +144 -0
  166. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/page-category.ts +28 -0
  167. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/page-tree-pane.tsx +431 -0
  168. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/pinned-pages.ts +54 -0
  169. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/presentation.ts +119 -0
  170. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/queries.ts +548 -0
  171. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/revalidate-paths.ts +70 -0
  172. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/sanity.cli.ts +29 -0
  173. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/sanity.config.ts +116 -0
  174. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/index.ts +6 -0
  175. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/reusable/blog-content.ts +89 -0
  176. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/reusable/description.ts +29 -0
  177. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/reusable/gated-content.ts +178 -0
  178. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/reusable/hero.ts +28 -0
  179. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/reusable/rich-text-block.ts +39 -0
  180. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/singleton/content-collection.ts +45 -0
  181. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/content/author.ts +70 -0
  182. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/content/blog-category.ts +55 -0
  183. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/index.ts +134 -0
  184. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/layout/company-data.ts +70 -0
  185. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/layout/footer.ts +79 -0
  186. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/layout/navbar.ts +75 -0
  187. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/layout/redirect.ts +152 -0
  188. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/layout/structured-data.ts +124 -0
  189. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/link.ts +125 -0
  190. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/logo-field.ts +9 -0
  191. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/metadata.ts +132 -0
  192. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/nav-objects.ts +192 -0
  193. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/page-builder.ts +43 -0
  194. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/page-folder.ts +124 -0
  195. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/page.ts +265 -0
  196. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/richText.ts +63 -0
  197. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/structured-data-blocks.ts +436 -0
  198. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/singletons.ts +51 -0
  199. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/structure.ts +334 -0
  200. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/tags.ts +89 -0
  201. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/utils/image.ts +8 -0
  202. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/utils/link.ts +140 -0
  203. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/utils/page-builder-markdown.ts +113 -0
  204. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/utils/page-json-ld.ts +208 -0
  205. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/scripts/sanity-typegen.ts +36 -0
  206. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/utils/check-redirect.ts +30 -0
  207. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/utils/json-ld.tsx +522 -0
  208. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/utils/metadata.ts +189 -0
  209. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/utils/rss.ts +96 -0
  210. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/utils/sitemap.ts +58 -0
  211. package/src/modules/features/cms/sanity-pagebuilder-cache/files/proxy.ts +90 -0
  212. package/src/modules/features/env/dotenvx/files/vercelignore +5 -0
  213. package/src/templates/next-default/.env.example +4 -2
  214. package/src/templates/next-default/.github/workflows/ci.yml +1 -1
  215. package/src/templates/next-default/.vscode/settings.json +0 -2
  216. package/src/templates/next-default/app/robots.ts +2 -3
  217. package/src/templates/next-default/components/ui/image/index.tsx +2 -4
  218. package/src/templates/next-default/knip.json +1 -1
  219. package/src/templates/next-default/lefthook.yml +1 -1
  220. package/src/templates/next-default/lib/utils/README.md +18 -5
  221. package/src/templates/next-default/lib/utils/image-sizes.ts +72 -0
  222. package/src/templates/next-default/lib/utils/merge-refs.ts +56 -0
  223. package/src/templates/next-default/lib/utils/strings.ts +1 -51
  224. package/src/templates/next-default/next.config.ts +38 -5
  225. package/src/templates/next-default/package.json +9 -12
  226. package/src/templates/next-experiments/.env.example +4 -2
  227. package/src/templates/next-experiments/.github/workflows/ci.yml +1 -1
  228. package/src/templates/next-experiments/.vscode/settings.json +0 -2
  229. package/src/templates/next-experiments/app/page.tsx +1 -1
  230. package/src/templates/next-experiments/app/robots.ts +2 -3
  231. package/src/templates/next-experiments/components/ui/image/index.tsx +2 -4
  232. package/src/templates/next-experiments/knip.json +1 -1
  233. package/src/templates/next-experiments/lefthook.yml +1 -1
  234. package/src/templates/next-experiments/lib/utils/README.md +18 -5
  235. package/src/templates/next-experiments/lib/utils/image-sizes.ts +72 -0
  236. package/src/templates/next-experiments/lib/utils/merge-refs.ts +56 -0
  237. package/src/templates/next-experiments/lib/utils/strings.ts +1 -51
  238. package/src/templates/next-experiments/next.config.ts +38 -5
  239. package/src/templates/next-experiments/package.json +9 -12
  240. package/src/templates/next-pagebuilder/.env.example +3 -0
  241. package/src/templates/next-pagebuilder/.github/workflows/ci.yml +1 -1
  242. package/src/templates/next-pagebuilder/.vscode/settings.json +0 -2
  243. package/src/templates/next-pagebuilder/README.md +95 -0
  244. package/src/templates/next-pagebuilder/app/(content)/[[...slug]]/page.tsx +24 -9
  245. package/src/templates/next-pagebuilder/app/robots.ts +2 -3
  246. package/src/templates/next-pagebuilder/app/sitemap.xml/route.ts +9 -4
  247. package/src/templates/next-pagebuilder/biome.json +2 -1
  248. package/src/templates/next-pagebuilder/components/layout/footer/index.tsx +3 -6
  249. package/src/templates/next-pagebuilder/components/layout/header/index.tsx +3 -2
  250. package/src/templates/next-pagebuilder/components/layout/json-ld/index.tsx +3 -8
  251. package/src/templates/next-pagebuilder/components/page-builder/components/content-collection/content-filters.tsx +23 -40
  252. package/src/templates/next-pagebuilder/components/page-builder/components/content-collection/index.tsx +88 -58
  253. package/src/templates/next-pagebuilder/components/page-builder/components/content-collection/skeleton.tsx +41 -0
  254. package/src/templates/next-pagebuilder/components/page-builder/components/content-collection/types.ts +14 -1
  255. package/src/templates/next-pagebuilder/components/page-builder/components/rich-text-block.tsx +16 -0
  256. package/src/templates/next-pagebuilder/components/page-builder/renderer.tsx +23 -3
  257. package/src/templates/next-pagebuilder/components/page-document/index.tsx +1 -0
  258. package/src/templates/next-pagebuilder/components/ui/image/index.tsx +2 -1
  259. package/src/templates/next-pagebuilder/knip.json +3 -2
  260. package/src/templates/next-pagebuilder/lefthook.yml +1 -1
  261. package/src/templates/next-pagebuilder/lib/integrations/sanity/sanity.types.ts +1221 -977
  262. package/src/templates/next-pagebuilder/lib/utils/image-sizes.ts +72 -0
  263. package/src/templates/next-pagebuilder/lib/utils/merge-refs.ts +56 -0
  264. package/src/templates/next-pagebuilder/next.config.ts +35 -5
  265. package/src/templates/next-pagebuilder/package.json +8 -10
  266. package/src/templates/next-pagebuilder-cache/.biome/plugins/README.md +21 -0
  267. package/src/templates/next-pagebuilder-cache/.biome/plugins/no-anchor-element.grit +12 -0
  268. package/src/templates/next-pagebuilder-cache/.biome/plugins/no-relative-parent-imports.grit +10 -0
  269. package/src/templates/next-pagebuilder-cache/.biome/plugins/no-unnecessary-forwardref.grit +9 -0
  270. package/src/templates/next-pagebuilder-cache/.env.example +40 -0
  271. package/src/templates/next-pagebuilder-cache/.github/PULL_REQUEST_TEMPLATE.md +13 -0
  272. package/src/templates/next-pagebuilder-cache/.github/workflows/ci.yml +105 -0
  273. package/src/templates/next-pagebuilder-cache/.vscode/extensions.json +19 -0
  274. package/src/templates/next-pagebuilder-cache/.vscode/settings.json +103 -0
  275. package/src/templates/next-pagebuilder-cache/.zed/settings.json +47 -0
  276. package/src/templates/next-pagebuilder-cache/README.md +212 -0
  277. package/src/templates/next-pagebuilder-cache/_gitignore +65 -0
  278. package/src/templates/next-pagebuilder-cache/app/(content)/[[...slug]]/page.tsx +124 -0
  279. package/src/templates/next-pagebuilder-cache/app/(content)/layout.tsx +28 -0
  280. package/src/templates/next-pagebuilder-cache/app/actions/unlock-gated-content.ts +53 -0
  281. package/src/templates/next-pagebuilder-cache/app/favicon.ico +0 -0
  282. package/src/templates/next-pagebuilder-cache/app/layout.tsx +83 -0
  283. package/src/templates/next-pagebuilder-cache/app/robots.ts +13 -0
  284. package/src/templates/next-pagebuilder-cache/app/sitemap.xml/route.ts +84 -0
  285. package/src/templates/next-pagebuilder-cache/biome.json +257 -0
  286. package/src/templates/next-pagebuilder-cache/components/layout/footer/index.tsx +127 -0
  287. package/src/templates/next-pagebuilder-cache/components/layout/header/components/cta-button.tsx +28 -0
  288. package/src/templates/next-pagebuilder-cache/components/layout/header/components/mega-menu-panel.tsx +90 -0
  289. package/src/templates/next-pagebuilder-cache/components/layout/header/components/nav-item-renderer.tsx +98 -0
  290. package/src/templates/next-pagebuilder-cache/components/layout/header/components/nav-leaf-item.tsx +33 -0
  291. package/src/templates/next-pagebuilder-cache/components/layout/header/components/types.ts +7 -0
  292. package/src/templates/next-pagebuilder-cache/components/layout/header/header-client.tsx +110 -0
  293. package/src/templates/next-pagebuilder-cache/components/layout/header/index.tsx +32 -0
  294. package/src/templates/next-pagebuilder-cache/components/layout/json-ld/index.tsx +79 -0
  295. package/src/templates/next-pagebuilder-cache/components/layout/wrapper/index.tsx +33 -0
  296. package/src/templates/next-pagebuilder-cache/components/page-builder/components/article-content/index.tsx +78 -0
  297. package/src/templates/next-pagebuilder-cache/components/page-builder/components/article-content/related-post-item.tsx +27 -0
  298. package/src/templates/next-pagebuilder-cache/components/page-builder/components/content-collection/content-card.tsx +66 -0
  299. package/src/templates/next-pagebuilder-cache/components/page-builder/components/content-collection/content-filters.tsx +76 -0
  300. package/src/templates/next-pagebuilder-cache/components/page-builder/components/content-collection/content-grid.tsx +40 -0
  301. package/src/templates/next-pagebuilder-cache/components/page-builder/components/content-collection/content-pagination-nav.tsx +71 -0
  302. package/src/templates/next-pagebuilder-cache/components/page-builder/components/content-collection/index.tsx +306 -0
  303. package/src/templates/next-pagebuilder-cache/components/page-builder/components/content-collection/skeleton.tsx +41 -0
  304. package/src/templates/next-pagebuilder-cache/components/page-builder/components/content-collection/types.ts +32 -0
  305. package/src/templates/next-pagebuilder-cache/components/page-builder/components/description.tsx +17 -0
  306. package/src/templates/next-pagebuilder-cache/components/page-builder/components/gated-content/index.tsx +169 -0
  307. package/src/templates/next-pagebuilder-cache/components/page-builder/components/hero.tsx +17 -0
  308. package/src/templates/next-pagebuilder-cache/components/page-builder/components/rich-text-block.tsx +16 -0
  309. package/src/templates/next-pagebuilder-cache/components/page-builder/renderer.tsx +71 -0
  310. package/src/templates/next-pagebuilder-cache/components/page-builder/types.ts +25 -0
  311. package/src/templates/next-pagebuilder-cache/components/page-document/index.tsx +147 -0
  312. package/src/templates/next-pagebuilder-cache/components/ui/image/index.tsx +216 -0
  313. package/src/templates/next-pagebuilder-cache/components/ui/link/index.tsx +152 -0
  314. package/src/templates/next-pagebuilder-cache/components.json +24 -0
  315. package/src/templates/next-pagebuilder-cache/knip.json +35 -0
  316. package/src/templates/next-pagebuilder-cache/lefthook.yml +25 -0
  317. package/src/templates/next-pagebuilder-cache/lib/integrations/sanity/sanity.types.ts +1588 -0
  318. package/src/templates/next-pagebuilder-cache/lib/scripts/setup-ci-secrets.ts +117 -0
  319. package/src/templates/next-pagebuilder-cache/lib/styles/cn.ts +5 -0
  320. package/src/templates/next-pagebuilder-cache/lib/styles/global.css +69 -0
  321. package/src/templates/next-pagebuilder-cache/lib/utils/base-url.ts +17 -0
  322. package/src/templates/next-pagebuilder-cache/lib/utils/format-date.ts +8 -0
  323. package/src/templates/next-pagebuilder-cache/lib/utils/slug-tag.ts +6 -0
  324. package/src/templates/next-pagebuilder-cache/next.config.ts +176 -0
  325. package/src/templates/next-pagebuilder-cache/package.json +62 -0
  326. package/src/templates/next-pagebuilder-cache/postcss.config.mjs +65 -0
  327. package/src/templates/next-pagebuilder-cache/svg.d.ts +6 -0
  328. package/src/templates/next-pagebuilder-cache/tsconfig.json +38 -0
  329. package/src/templates/next-webgl/.env.example +4 -2
  330. package/src/templates/next-webgl/.github/workflows/ci.yml +1 -1
  331. package/src/templates/next-webgl/.vscode/settings.json +0 -2
  332. package/src/templates/next-webgl/app/robots.ts +2 -3
  333. package/src/templates/next-webgl/components/ui/image/index.tsx +2 -4
  334. package/src/templates/next-webgl/knip.json +1 -1
  335. package/src/templates/next-webgl/lefthook.yml +1 -1
  336. package/src/templates/next-webgl/lib/utils/README.md +18 -5
  337. package/src/templates/next-webgl/lib/utils/image-sizes.ts +72 -0
  338. package/src/templates/next-webgl/lib/utils/merge-refs.ts +56 -0
  339. package/src/templates/next-webgl/lib/utils/strings.ts +1 -51
  340. package/src/templates/next-webgl/next.config.ts +38 -5
  341. package/src/templates/next-webgl/package.json +11 -14
  342. package/src/templates/next-experiments/tsconfig.tsbuildinfo +0 -1
  343. package/src/templates/next-pagebuilder/app/actions/refresh.ts +0 -5
  344. package/src/templates/next-webgl/tsconfig.tsbuildinfo +0 -1
@@ -0,0 +1,66 @@
1
+ import { cacheLife, cacheTag } from "next/cache";
2
+ import { ALL_BLOG_ARTICLES_QUERY } from "@/lib/integrations/sanity/queries";
3
+ import type { ALL_BLOG_ARTICLES_QUERY_RESULT } from "@/lib/integrations/sanity/sanity.types";
4
+ import { FEED_TAG } from "@/lib/integrations/sanity/tags";
5
+ import { generateRssFeed, rssErrorResponse } from "@/lib/utils/rss";
6
+ import { getPageUrl, getSitemapBaseUrl } from "@/lib/utils/sitemap";
7
+
8
+ // Cached and tagged rather than read per request: feed readers poll.
9
+ const getArticles = async () => {
10
+ "use cache";
11
+ const sanityModule = await import("@/lib/integrations/sanity/client");
12
+ const client = sanityModule?.client;
13
+
14
+ if (!client) return null;
15
+
16
+ // Explicit generic: next-sanity 13.3 doesn't infer it for a dynamic import.
17
+ const articles = await client
18
+ .fetch<ALL_BLOG_ARTICLES_QUERY_RESULT>(ALL_BLOG_ARTICLES_QUERY)
19
+ .catch((error) => {
20
+ console.error("Error fetching articles for the feed:", error);
21
+ return null;
22
+ });
23
+
24
+ if (articles) {
25
+ cacheTag(FEED_TAG);
26
+ } else {
27
+ // No publish clears a failed read, and this prerenders — otherwise a build-time
28
+ // outage would serve an error feed for the full ~1y cacheLife.
29
+ cacheLife("hours");
30
+ }
31
+
32
+ return articles;
33
+ };
34
+
35
+ // No `Cache-Control`: a CDN copy is a second cache the webhook can't purge.
36
+ export async function GET() {
37
+ const baseUrl = getSitemapBaseUrl();
38
+
39
+ try {
40
+ const articles = await getArticles();
41
+ if (!articles) return rssErrorResponse();
42
+
43
+ const items = articles
44
+ .filter((article) => article.resolvedSlug && !article.noIndex)
45
+ .map((article) => ({
46
+ title: article.title ?? "Untitled",
47
+ link: getPageUrl(baseUrl, article.resolvedSlug),
48
+ description: article.metadata?.metaDescription ?? undefined,
49
+ pubDate: article.blogContent?.date ?? undefined,
50
+ authors: article.blogContent?.author?.name
51
+ ? [article.blogContent.author.name]
52
+ : undefined,
53
+ }));
54
+
55
+ return generateRssFeed({
56
+ title: `${new URL(baseUrl).hostname} Blog`,
57
+ link: `${baseUrl}/blog`,
58
+ description: "Latest articles",
59
+ feedUrl: `${baseUrl}/feed.xml`,
60
+ items,
61
+ });
62
+ } catch (error) {
63
+ console.error("Error generating RSS feed:", error);
64
+ return rssErrorResponse();
65
+ }
66
+ }
@@ -0,0 +1,132 @@
1
+ import { NextResponse } from "next/server";
2
+ import { isSanityConfigured } from "@/lib/integrations/check-integration";
3
+ import type { PAGE_SITEMAP_QUERY_RESULT } from "@/lib/integrations/sanity/sanity.types";
4
+ import {
5
+ getMarkdownPath,
6
+ getSitemap,
7
+ getSitemapBaseUrl,
8
+ } from "@/lib/utils/sitemap";
9
+
10
+ type PageDoc = PAGE_SITEMAP_QUERY_RESULT[number];
11
+
12
+ const ROOT_GROUP = "__root__";
13
+
14
+ const getFolderGroup = (slug: string | null) => {
15
+ if (!slug) return ROOT_GROUP;
16
+
17
+ const segments = slug.split("/").filter(Boolean);
18
+
19
+ if (segments.length <= 1) return ROOT_GROUP;
20
+
21
+ return segments.slice(0, -1).join("/");
22
+ };
23
+
24
+ const getGroupHeading = (group: string) => {
25
+ if (group === ROOT_GROUP) return "### Root Pages";
26
+
27
+ return `### /${group}`;
28
+ };
29
+
30
+ const sortPages = (left: PageDoc, right: PageDoc) => {
31
+ const leftLabel = left.title || left.slug || "Homepage";
32
+ const rightLabel = right.title || right.slug || "Homepage";
33
+
34
+ return leftLabel.localeCompare(rightLabel);
35
+ };
36
+
37
+ // Cached, not per-request: this bills itself as being for AI agents, so its
38
+ // traffic is crawler-driven. `getSitemap` tags the read; the webhook busts it.
39
+ export const GET = async () => {
40
+ const baseUrl = getSitemapBaseUrl();
41
+
42
+ if (!isSanityConfigured()) {
43
+ return new NextResponse(
44
+ [
45
+ "# Sitemap",
46
+ "",
47
+ "## Pages",
48
+ "",
49
+ `- [Homepage](${baseUrl}/index.md)`,
50
+ "",
51
+ "CMS not configured. No additional content available.",
52
+ ].join("\n"),
53
+ {
54
+ headers: { "Content-Type": "text/markdown; charset=utf-8" },
55
+ },
56
+ );
57
+ }
58
+
59
+ try {
60
+ const allPages = await getSitemap();
61
+
62
+ if (!allPages) {
63
+ return new NextResponse("# Sitemap\n\nUnable to connect to CMS.", {
64
+ status: 503,
65
+ headers: { "Content-Type": "text/markdown; charset=utf-8" },
66
+ });
67
+ }
68
+
69
+ const pages = (allPages ?? []).filter((page) => !page.noIndex);
70
+ const pageGroups = new Map<string, PageDoc[]>();
71
+
72
+ for (const page of pages) {
73
+ const group = getFolderGroup(page.slug ?? null);
74
+ const groupedPages = pageGroups.get(group) ?? [];
75
+
76
+ groupedPages.push(page);
77
+ pageGroups.set(group, groupedPages);
78
+ }
79
+
80
+ const sortedGroupNames = [...pageGroups.keys()].sort((left, right) => {
81
+ if (left === ROOT_GROUP) return -1;
82
+ if (right === ROOT_GROUP) return 1;
83
+
84
+ return left.localeCompare(right);
85
+ });
86
+
87
+ const parts = [
88
+ `# ${new URL(baseUrl).hostname} - Content Sitemap`,
89
+ "",
90
+ "> This sitemap lists all available markdown content for AI agents.",
91
+ "",
92
+ "## Pages",
93
+ "",
94
+ ];
95
+
96
+ if (pages.length > 0) {
97
+ for (const groupName of sortedGroupNames) {
98
+ const groupedPages = (pageGroups.get(groupName) ?? []).sort(sortPages);
99
+
100
+ if (groupedPages.length === 0) continue;
101
+
102
+ parts.push(getGroupHeading(groupName), "");
103
+
104
+ for (const page of groupedPages) {
105
+ parts.push(
106
+ `- [${page.title || page.slug || "Homepage"}](${getMarkdownPath(baseUrl, page.slug ?? null)})`,
107
+ );
108
+ }
109
+
110
+ parts.push("");
111
+ }
112
+ }
113
+
114
+ if (pages.length === 0) parts.push("No content published yet.", "");
115
+
116
+ // No `Cache-Control`: an `s-maxage` would be a second cache the webhook can't
117
+ // purge, so a publish would clear this entry and the CDN would serve the old
118
+ // markdown for another hour regardless.
119
+ return new NextResponse(parts.join("\n"), {
120
+ headers: {
121
+ "Content-Type": "text/markdown; charset=utf-8",
122
+ "X-Content-Type-Options": "nosniff",
123
+ },
124
+ });
125
+ } catch (error) {
126
+ console.error("Error generating sitemap.md:", error);
127
+ return new NextResponse("# Sitemap\n\nError fetching content.", {
128
+ status: 500,
129
+ headers: { "Content-Type": "text/markdown; charset=utf-8" },
130
+ });
131
+ }
132
+ };
@@ -0,0 +1,26 @@
1
+ import { connection } from "next/server";
2
+ import { type ReactNode, Suspense } from "react";
3
+
4
+ // Re-export next-sanity's static metadata (incl. robots: noindex) — the
5
+ // "use client" Studio page can't export metadata itself.
6
+ export { metadata, viewport } from "next-sanity/studio";
7
+
8
+ // File-based metadata on a catch-all depends on the route param, so the Studio's
9
+ // metadata is dynamic while its body is static — which Cache Components rejects.
10
+ // It's an interactive SPA with no SEO value, so mark it dynamic behind its own
11
+ // boundary and let the rest of the shell prerender.
12
+ async function DynamicMarker() {
13
+ await connection();
14
+ return null;
15
+ }
16
+
17
+ export default function StudioLayout({ children }: { children: ReactNode }) {
18
+ return (
19
+ <>
20
+ {children}
21
+ <Suspense>
22
+ <DynamicMarker />
23
+ </Suspense>
24
+ </>
25
+ );
26
+ }
@@ -0,0 +1,23 @@
1
+ "use client";
2
+
3
+ import { NextStudio } from "next-sanity/studio";
4
+ import { isSanityConfigured } from "@/lib/integrations/check-integration";
5
+ import config from "@/lib/integrations/sanity/sanity.config";
6
+
7
+ // The Studio throws on an empty `projectId`, which `env.ts` falls back to when
8
+ // Sanity isn't configured — so this route would fail `next build`.
9
+ const StudioPage = () =>
10
+ isSanityConfigured() ? (
11
+ <NextStudio config={config} />
12
+ ) : (
13
+ <main style={{ padding: "2rem", fontFamily: "system-ui, sans-serif" }}>
14
+ <h1>Studio unavailable</h1>
15
+ <p>
16
+ Set <code>NEXT_PUBLIC_SANITY_PROJECT_ID</code> and{" "}
17
+ <code>NEXT_PUBLIC_SANITY_DATASET</code>, then restart, to load the
18
+ embedded Studio.
19
+ </p>
20
+ </main>
21
+ );
22
+
23
+ export default StudioPage;
@@ -0,0 +1,27 @@
1
+ "use client";
2
+
3
+ import { useRouter } from "next/navigation";
4
+ import { useTransition } from "react";
5
+
6
+ export function DraftModeToggle() {
7
+ const router = useRouter();
8
+ const [isPending, startTransition] = useTransition();
9
+
10
+ const handleDisable = () => {
11
+ startTransition(async () => {
12
+ await fetch("/api/draft-mode/disable");
13
+ router.refresh();
14
+ });
15
+ };
16
+
17
+ return (
18
+ <button
19
+ type="button"
20
+ onClick={handleDisable}
21
+ disabled={isPending}
22
+ className="fixed bottom-4 left-1/2 z-50 -translate-x-1/2 rounded-full border border-white/10 bg-black/80 px-2 py-1 font-medium text-sm text-white shadow-lg backdrop-blur-sm transition-colors hover:bg-black/90 disabled:opacity-50"
23
+ >
24
+ {isPending ? "Disabling..." : "Disable Draft Mode"}
25
+ </button>
26
+ );
27
+ }
@@ -0,0 +1,88 @@
1
+ import { PortableText, type PortableTextProps } from "@portabletext/react";
2
+ import { Link } from "@/components/ui/link";
3
+ import { SanityImage } from "@/components/ui/sanity-image";
4
+ import {
5
+ getLinkAttributes,
6
+ urlForReference,
7
+ } from "../../lib/integrations/sanity/utils/link";
8
+
9
+ interface RichTextProps {
10
+ content: PortableTextProps["value"];
11
+ }
12
+
13
+ type LinkMarkProps = {
14
+ children: React.ReactNode;
15
+ value?: unknown;
16
+ };
17
+
18
+ const LinkMark = ({ children, value }: LinkMarkProps) => {
19
+ const href = urlForReference(value as Parameters<typeof urlForReference>[0]);
20
+ const attrs = getLinkAttributes(
21
+ value as Parameters<typeof getLinkAttributes>[0],
22
+ );
23
+
24
+ return (
25
+ <Link
26
+ href={href}
27
+ target={attrs.target}
28
+ rel={attrs.rel}
29
+ className="text-blue underline underline-offset-2 transition-opacity hover:opacity-70"
30
+ data-sanity-edit-target
31
+ >
32
+ {children}
33
+ </Link>
34
+ );
35
+ };
36
+
37
+ export const RichText = ({ content }: RichTextProps) => {
38
+ if (!content) return null;
39
+
40
+ return (
41
+ <PortableText
42
+ value={content}
43
+ components={{
44
+ types: {
45
+ image: ({ value }) => <SanityImage image={value} maxWidth={1920} />,
46
+ table: ({ value }) => {
47
+ const rows = value?.rows as
48
+ | Array<{ _key?: string; cells?: string[] }>
49
+ | undefined;
50
+ if (!rows?.length) return null;
51
+ return (
52
+ <table>
53
+ <tbody>
54
+ {rows.map((row, i) => (
55
+ <tr key={row._key ?? `row-${i}`}>
56
+ {row.cells?.map((cell) => {
57
+ const cellKey = `${row._key}-${cell}`;
58
+ return i === 0 ? (
59
+ <th key={cellKey}>{cell}</th>
60
+ ) : (
61
+ <td key={cellKey}>{cell}</td>
62
+ );
63
+ })}
64
+ </tr>
65
+ ))}
66
+ </tbody>
67
+ </table>
68
+ );
69
+ },
70
+ },
71
+ marks: {
72
+ link: LinkMark,
73
+ pageReference: LinkMark,
74
+ externalLink: LinkMark,
75
+ },
76
+ block: {
77
+ h1: ({ children }) => <h1 className="h1">{children}</h1>,
78
+ h2: ({ children }) => <h2 className="h2">{children}</h2>,
79
+ h3: ({ children }) => <h3 className="h3">{children}</h3>,
80
+ h4: ({ children }) => <h4 className="h4">{children}</h4>,
81
+ h5: ({ children }) => <h5 className="h5">{children}</h5>,
82
+ h6: ({ children }) => <h6 className="h6">{children}</h6>,
83
+ normal: ({ children }) => <p className="p">{children}</p>,
84
+ },
85
+ }}
86
+ />
87
+ );
88
+ };
@@ -0,0 +1,11 @@
1
+ import { VisualEditing } from "next-sanity/visual-editing";
2
+ import { DraftModeToggle } from "@/components/sanity/draft-mode-toggle";
3
+
4
+ // Rendered by the content layout only when draft mode is enabled. <SanityLive>
5
+ // lives in the layout so there is exactly one of each across the tree.
6
+ export const SanityVisualEditing = () => (
7
+ <>
8
+ <DraftModeToggle />
9
+ <VisualEditing />
10
+ </>
11
+ );
@@ -0,0 +1,51 @@
1
+ import { getImageDimensions } from "@sanity/asset-utils";
2
+ import { Image, type ImageProps } from "@/components/ui/image";
3
+ import { urlForImage } from "@/lib/integrations/sanity/utils/image";
4
+
5
+ interface SanityImageProps extends Omit<ImageProps, "src" | "aspectRatio"> {
6
+ image: {
7
+ asset?: {
8
+ _ref: string;
9
+ _type: "reference";
10
+ };
11
+ alt?: string;
12
+ hotspot?: object;
13
+ crop?: { left?: number; right?: number; top?: number; bottom?: number };
14
+ };
15
+ maxWidth?: number;
16
+ }
17
+
18
+ export function SanityImage({
19
+ image,
20
+ maxWidth = 1920,
21
+ alt,
22
+ fill,
23
+ ...props
24
+ }: SanityImageProps) {
25
+ if (!image?.asset) return null;
26
+
27
+ // getImageDimensions is uncropped; the URL builder applies the crop.
28
+ // Rounding order must match @sanity/image-url: origin first, then subtract.
29
+ const { width, height } = getImageDimensions(image.asset);
30
+ const { left = 0, right = 0, top = 0, bottom = 0 } = image.crop ?? {};
31
+ const cropLeft = Math.round(left * width);
32
+ const cropTop = Math.round(top * height);
33
+ const sourceWidth = Math.round(width - right * width - cropLeft);
34
+ const sourceHeight = Math.round(height - bottom * height - cropTop);
35
+
36
+ const imageProps = fill
37
+ ? { fill: true }
38
+ : { width: sourceWidth, height: sourceHeight };
39
+
40
+ return (
41
+ <Image
42
+ src={urlForImage(image)
43
+ .width(Math.min(maxWidth, sourceWidth))
44
+ .url()}
45
+ alt={alt || image.alt || ""}
46
+ aspectRatio={sourceWidth / sourceHeight}
47
+ {...imageProps}
48
+ {...props}
49
+ />
50
+ );
51
+ }
@@ -0,0 +1,6 @@
1
+ import { dataset, projectId } from "@/lib/integrations/sanity/env";
2
+
3
+ // Derived from env.ts, not a second read of `process.env`. Read directly these
4
+ // disagreed — env.ts also accepts `SANITY_STUDIO_PROJECT_ID` and defaults the
5
+ // dataset — so a resolvable project reported itself unconfigured.
6
+ export const isSanityConfigured = () => Boolean(projectId && dataset);
@@ -0,0 +1,37 @@
1
+ import { createClient, type SanityClient } from "next-sanity";
2
+ import { isSanityConfigured } from "@/lib/integrations/check-integration";
3
+ import { apiVersion, dataset, projectId, sanityToken, studioUrl } from "./env";
4
+
5
+ /**
6
+ * Sanity client instance
7
+ *
8
+ * Returns null if Sanity is not configured (missing env vars).
9
+ * Always check with isSanityConfigured() before using.
10
+ */
11
+ export const client: SanityClient | null = isSanityConfigured()
12
+ ? createClient({
13
+ projectId,
14
+ dataset,
15
+ apiVersion,
16
+ useCdn: true,
17
+ perspective: "published",
18
+ token: sanityToken,
19
+ stega: {
20
+ studioUrl,
21
+ filter: (props) => {
22
+ if (props.sourcePath.at(-1) === "title") return true;
23
+ return props.filterDefault(props);
24
+ },
25
+ },
26
+ })
27
+ : null;
28
+
29
+ /**
30
+ * Non-CDN clone for the publish webhook, and only for it: a CDN hit would return
31
+ * the pre-mutation slug, so it would invalidate the old path and miss the new one.
32
+ *
33
+ * Elsewhere outside a `'use cache'` scope, use plain `client.fetch` — bypassing
34
+ * the CDN costs uncached-API quota for no freshness anyone needs.
35
+ */
36
+ export const directClient: SanityClient | null =
37
+ client?.withConfig({ useCdn: false }) ?? null;
@@ -0,0 +1,81 @@
1
+ import { EyeClosedIcon, EyeOpenIcon } from "@sanity/icons";
2
+ import { Badge, Box, Button, Flex, Text, Tooltip, useToast } from "@sanity/ui";
3
+ import { useState } from "react";
4
+ import { type ObjectItemProps, useClient, useFormValue } from "sanity";
5
+ import { apiVersion } from "@/lib/integrations/sanity/env";
6
+
7
+ const HIDDEN_KEYS_FIELD = "hiddenPageBuilderKeys";
8
+
9
+ type ReferenceItem = { _key: string; _ref: string; _type: string };
10
+
11
+ /**
12
+ * Eye-icon toggle for a page builder placement's per-page visibility. Adds the
13
+ * placement's `_key` to the owning document's `hiddenPageBuilderKeys`; GROQ
14
+ * filters those keys out at query time. Standalone control because Sanity's
15
+ * built-in item menu isn't extensible via public props.
16
+ */
17
+ export function BlockActionsItem(props: ObjectItemProps<ReferenceItem>) {
18
+ const { value, renderDefault } = props;
19
+ const client = useClient({ apiVersion });
20
+ const toast = useToast();
21
+ const [busy, setBusy] = useState(false);
22
+
23
+ // Open document id (draft while editing).
24
+ const documentId = useFormValue(["_id"]) as string | undefined;
25
+ const hiddenKeys =
26
+ (useFormValue([HIDDEN_KEYS_FIELD]) as string[] | undefined) ?? [];
27
+ const itemKey = value._key;
28
+ const hidden = hiddenKeys.includes(itemKey);
29
+ const label = hidden ? "Show on this page" : "Hide on this page";
30
+
31
+ async function handleToggleHidden() {
32
+ if (busy || !documentId) return;
33
+ setBusy(true);
34
+ try {
35
+ // Atomic array ops (not read-modify-write) so concurrent toggles
36
+ // of different blocks don't clobber each other.
37
+ const patch = client.patch(documentId);
38
+ if (hidden) {
39
+ patch.unset([`${HIDDEN_KEYS_FIELD}[@ == "${itemKey}"]`]);
40
+ } else {
41
+ patch
42
+ .setIfMissing({ [HIDDEN_KEYS_FIELD]: [] })
43
+ .insert("after", `${HIDDEN_KEYS_FIELD}[-1]`, [itemKey]);
44
+ }
45
+ await patch.commit();
46
+ toast.push({
47
+ status: "success",
48
+ title: hidden ? "Block shown" : "Block hidden",
49
+ });
50
+ } catch {
51
+ toast.push({ status: "error", title: "Failed to update visibility" });
52
+ } finally {
53
+ setBusy(false);
54
+ }
55
+ }
56
+
57
+ return (
58
+ <Flex align="center" gap={1} paddingLeft={1}>
59
+ <Tooltip content={<Text size={1}>{label}</Text>} placement="top" portal>
60
+ <Button
61
+ mode="bleed"
62
+ // Open eye = visible, closed eye = hidden.
63
+ icon={hidden ? EyeClosedIcon : EyeOpenIcon}
64
+ tone={hidden ? "caution" : "default"}
65
+ aria-label={label}
66
+ // Unsaved doc has no id to patch — disable, don't no-op.
67
+ disabled={busy || !documentId}
68
+ onClick={handleToggleHidden}
69
+ />
70
+ </Tooltip>
71
+ {hidden && (
72
+ <Badge fontSize={0} mode="outline" tone="caution">
73
+ Hidden
74
+ </Badge>
75
+ )}
76
+ <Box flex={1} style={hidden ? { opacity: 0.5 } : undefined}>
77
+ {renderDefault(props)}
78
+ </Box>
79
+ </Flex>
80
+ );
81
+ }
@@ -0,0 +1,23 @@
1
+ "use client";
2
+
3
+ import Link from "next/link";
4
+ import { usePathname } from "next/navigation";
5
+ import { useIsPresentationTool } from "next-sanity/hooks";
6
+
7
+ export function DisableDraftMode() {
8
+ const isPresentationTool = useIsPresentationTool();
9
+ const pathname = usePathname();
10
+
11
+ if (isPresentationTool) return null;
12
+ if (pathname.startsWith("/studio")) return null;
13
+
14
+ return (
15
+ <Link
16
+ href="/api/draft-mode/disable"
17
+ scroll={false}
18
+ className="dr-p-4 fixed top-safe right-safe z-50 bg-red font-mono text-primary text-sm uppercase"
19
+ >
20
+ Disable Draft Mode
21
+ </Link>
22
+ );
23
+ }
@@ -0,0 +1,39 @@
1
+ import { type ArrayOfObjectsInputProps, useFormValue } from "sanity";
2
+ import {
3
+ getVisiblePageBuilderInsertMenuGroups,
4
+ getVisiblePageBuilderReferenceMembers,
5
+ type PageBuilderGroupName,
6
+ } from "@/lib/integrations/sanity/page-builder-config";
7
+
8
+ export function PageBuilderInput(props: ArrayOfObjectsInputProps) {
9
+ const pageTypeValue = useFormValue(["type"]);
10
+ const pageType =
11
+ typeof pageTypeValue === "string"
12
+ ? (pageTypeValue as PageBuilderGroupName)
13
+ : undefined;
14
+
15
+ const visibleMemberNames = new Set(
16
+ getVisiblePageBuilderReferenceMembers(pageType).map(
17
+ (member) => member.name,
18
+ ),
19
+ );
20
+
21
+ return props.renderDefault({
22
+ ...props,
23
+ schemaType: {
24
+ ...props.schemaType,
25
+ of: props.schemaType.of.filter(
26
+ (member) =>
27
+ typeof member.name === "string" &&
28
+ visibleMemberNames.has(member.name),
29
+ ),
30
+ options: {
31
+ ...props.schemaType.options,
32
+ insertMenu: {
33
+ ...props.schemaType.options?.insertMenu,
34
+ groups: getVisiblePageBuilderInsertMenuGroups(pageType),
35
+ },
36
+ },
37
+ },
38
+ });
39
+ }
@@ -0,0 +1,50 @@
1
+ import { Select } from "@sanity/ui";
2
+ import { useEffect } from "react";
3
+ import { type StringInputProps, set, setIfMissing } from "sanity";
4
+
5
+ type PageCategoryOption = {
6
+ title: string;
7
+ value: string;
8
+ };
9
+
10
+ const isPageCategoryOption = (option: unknown): option is PageCategoryOption =>
11
+ typeof option === "object" &&
12
+ option !== null &&
13
+ "title" in option &&
14
+ "value" in option &&
15
+ typeof option.title === "string" &&
16
+ typeof option.value === "string";
17
+
18
+ export function PageCategoryInput(props: StringInputProps) {
19
+ useEffect(() => {
20
+ if (props.value === undefined) {
21
+ props.onChange(setIfMissing("generic"));
22
+ }
23
+ }, [props.onChange, props.value]);
24
+
25
+ const options = Array.isArray(props.schemaType.options?.list)
26
+ ? props.schemaType.options.list.filter(isPageCategoryOption)
27
+ : [];
28
+
29
+ if (options.length === 0) {
30
+ return props.renderDefault({ ...props, value: props.value ?? "generic" });
31
+ }
32
+
33
+ return (
34
+ <Select
35
+ {...props.elementProps}
36
+ disabled={props.readOnly}
37
+ onChange={(event) => props.onChange(set(event.currentTarget.value))}
38
+ value={props.value ?? "generic"}
39
+ {...(props.validationError
40
+ ? { customValidity: props.validationError }
41
+ : {})}
42
+ >
43
+ {options.map((option) => (
44
+ <option key={option.value} value={option.value}>
45
+ {option.title}
46
+ </option>
47
+ ))}
48
+ </Select>
49
+ );
50
+ }