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
@@ -0,0 +1,90 @@
1
+ import { Link } from "@/components/ui/link"
2
+ import { getLinkAttributes } from "@/lib/integrations/sanity/utils/link"
3
+ import { cn } from "@/lib/styles/cn"
4
+
5
+ import { NavLeafItem } from "./nav-leaf-item"
6
+ import type { MegaMenu, MegaMenuColumn as MegaMenuColumnType } from "./types"
7
+
8
+ function ColumnHeader({
9
+ title,
10
+ href,
11
+ target,
12
+ rel,
13
+ }: {
14
+ title: string
15
+ href: string | null
16
+ target?: string | undefined
17
+ rel?: string | undefined
18
+ }) {
19
+ const className =
20
+ "mb-1 block px-3 py-2 text-xs font-semibold uppercase tracking-wider text-gray-400"
21
+
22
+ if (href) {
23
+ return (
24
+ <Link
25
+ href={href}
26
+ target={target}
27
+ rel={rel}
28
+ className={cn(className, "transition-colors hover:text-black")}
29
+ >
30
+ {title}
31
+ </Link>
32
+ )
33
+ }
34
+
35
+ return <span className={className}>{title}</span>
36
+ }
37
+
38
+ function MegaMenuColumn({ column }: { column: MegaMenuColumnType }) {
39
+ const linkAttributes = column.link
40
+ ? getLinkAttributes(column.link)
41
+ : undefined
42
+ const href = linkAttributes?.href
43
+ const items = column.items
44
+
45
+ return (
46
+ <div className="flex min-w-48 flex-col">
47
+ {column.title ? (
48
+ <ColumnHeader
49
+ title={column.title}
50
+ href={href && href !== "#" ? href : null}
51
+ target={linkAttributes?.target}
52
+ rel={linkAttributes?.rel}
53
+ />
54
+ ) : null}
55
+
56
+ {items?.length ? (
57
+ <ul className="flex flex-col">
58
+ {items.map((item) => (
59
+ <li key={item._key}>
60
+ <NavLeafItem item={item} />
61
+ </li>
62
+ ))}
63
+ </ul>
64
+ ) : null}
65
+ </div>
66
+ )
67
+ }
68
+
69
+ export function MegaMenuPanel({
70
+ megaMenu,
71
+ id,
72
+ }: {
73
+ megaMenu: MegaMenu
74
+ id: string
75
+ }) {
76
+ return (
77
+ <div
78
+ id={id}
79
+ className="absolute top-full left-1/2 z-50 w-max max-w-[90vw] -translate-x-1/2 pt-2"
80
+ >
81
+ <div className="rounded-xl border border-black/10 bg-white p-6 shadow-black/5 shadow-xl">
82
+ <div className="flex gap-8">
83
+ {megaMenu.columns?.map((col) => (
84
+ <MegaMenuColumn key={col._key} column={col} />
85
+ ))}
86
+ </div>
87
+ </div>
88
+ </div>
89
+ )
90
+ }
@@ -0,0 +1,98 @@
1
+ import { CaretDownIcon } from "@phosphor-icons/react/dist/ssr"
2
+
3
+ import { Link } from "@/components/ui/link"
4
+ import { getLinkAttributes } from "@/lib/integrations/sanity/utils/link"
5
+ import { cn } from "@/lib/styles/cn"
6
+
7
+ import { MegaMenuPanel } from "./mega-menu-panel"
8
+ import type { NavItem } from "./types"
9
+
10
+ type NavItemRendererProps = {
11
+ item: NavItem
12
+ isOpen: boolean
13
+ onToggle: () => void
14
+ triggerRef: (el: HTMLButtonElement | null) => void
15
+ }
16
+
17
+ const baseLabelClassName = "text-sm transition-colors"
18
+ const interactiveLabelClassName = cn(
19
+ baseLabelClassName,
20
+ "text-gray-600 hover:text-black"
21
+ )
22
+
23
+ function getValidHref(item: NavItem) {
24
+ const linkAttributes = item.link ? getLinkAttributes(item.link) : undefined
25
+ const href =
26
+ linkAttributes?.href && linkAttributes.href !== "#"
27
+ ? linkAttributes.href
28
+ : null
29
+
30
+ return { href, linkAttributes }
31
+ }
32
+
33
+ export function NavItemRenderer({
34
+ item,
35
+ isOpen,
36
+ onToggle,
37
+ triggerRef,
38
+ }: NavItemRendererProps) {
39
+ const panelId = `mega-menu-${item._key}`
40
+
41
+ if (item.itemType === "mega-menu" && item.megaMenu) {
42
+ const megaMenu = item.megaMenu
43
+
44
+ return (
45
+ <li className="relative">
46
+ <button
47
+ ref={triggerRef}
48
+ type="button"
49
+ onClick={onToggle}
50
+ className={cn(
51
+ "flex items-center gap-1",
52
+ baseLabelClassName,
53
+ isOpen ? "text-black" : interactiveLabelClassName
54
+ )}
55
+ aria-expanded={isOpen}
56
+ aria-haspopup="true"
57
+ aria-controls={panelId}
58
+ >
59
+ {item.title}
60
+ <CaretDownIcon
61
+ size={14}
62
+ weight="bold"
63
+ className={cn(
64
+ "transition-transform duration-200",
65
+ isOpen && "rotate-180"
66
+ )}
67
+ />
68
+ </button>
69
+ {isOpen ? <MegaMenuPanel megaMenu={megaMenu} id={panelId} /> : null}
70
+ </li>
71
+ )
72
+ }
73
+
74
+ const { href, linkAttributes } = getValidHref(item)
75
+
76
+ if (href) {
77
+ return (
78
+ <li>
79
+ <Link
80
+ href={href}
81
+ target={linkAttributes?.target}
82
+ rel={linkAttributes?.rel}
83
+ className={interactiveLabelClassName}
84
+ >
85
+ {item.title}
86
+ </Link>
87
+ </li>
88
+ )
89
+ }
90
+
91
+ return (
92
+ <li>
93
+ <span className={cn(baseLabelClassName, "cursor-default text-gray-400")}>
94
+ {item.title}
95
+ </span>
96
+ </li>
97
+ )
98
+ }
@@ -0,0 +1,33 @@
1
+ import { Link } from "@/components/ui/link"
2
+ import { getLinkAttributes } from "@/lib/integrations/sanity/utils/link"
3
+
4
+ import type { LeafItem } from "./types"
5
+
6
+ export function NavLeafItem({ item }: { item: LeafItem }) {
7
+ const linkAttributes = item.link ? getLinkAttributes(item.link) : undefined
8
+ const href = linkAttributes?.href
9
+ const label = (
10
+ <span className="text-gray-700 text-sm transition-colors group-hover/leaf:text-black">
11
+ {item.title}
12
+ </span>
13
+ )
14
+
15
+ if (href && href !== "#") {
16
+ return (
17
+ <Link
18
+ href={href}
19
+ target={linkAttributes?.target}
20
+ rel={linkAttributes?.rel}
21
+ className="block rounded-md px-3 py-2 transition-colors hover:bg-gray-50"
22
+ >
23
+ {label}
24
+ </Link>
25
+ )
26
+ }
27
+
28
+ return (
29
+ <span className="block cursor-default rounded-md px-3 py-2 opacity-50">
30
+ {label}
31
+ </span>
32
+ )
33
+ }
@@ -0,0 +1,7 @@
1
+ import type { NavbarData } from "@/lib/integrations/sanity/fetchers/layout"
2
+
3
+ export type NavItem = NonNullable<NavbarData["navigationItems"]>[number]
4
+ export type MegaMenu = NonNullable<NavItem["megaMenu"]>
5
+ export type MegaMenuColumn = NonNullable<MegaMenu["columns"]>[number]
6
+ export type LeafItem = NonNullable<MegaMenuColumn["items"]>[number]
7
+ export type CtaButton = NonNullable<NavbarData["ctaButtons"]>[number]
@@ -0,0 +1,110 @@
1
+ "use client"
2
+
3
+ import { useEffect, useRef, useState } from "react"
4
+
5
+ import { Image } from "@/components/ui/image"
6
+ import { Link } from "@/components/ui/link"
7
+ import type { NavbarData } from "@/lib/integrations/sanity/fetchers/layout"
8
+
9
+ import { CtaButtonRenderer } from "./components/cta-button"
10
+ import { NavItemRenderer } from "./components/nav-item-renderer"
11
+
12
+ export const HeaderClient = ({ data: navbar }: { data: NavbarData | null }) => {
13
+ const [openIndex, setOpenIndex] = useState<number | null>(null)
14
+ const navRef = useRef<HTMLElement>(null)
15
+ const triggerRefs = useRef<Map<number, HTMLButtonElement>>(new Map())
16
+
17
+ const logoUrl = navbar?.logo?.asset?.url
18
+ const navigationItems = navbar?.navigationItems ?? []
19
+ const ctaButtons = navbar?.ctaButtons ?? []
20
+
21
+ // Close on click outside & Escape
22
+ useEffect(() => {
23
+ if (openIndex === null) return
24
+
25
+ const onClick = (e: MouseEvent) => {
26
+ if (
27
+ navRef.current &&
28
+ e.target instanceof Node &&
29
+ !navRef.current.contains(e.target)
30
+ ) {
31
+ setOpenIndex(null)
32
+ }
33
+ }
34
+
35
+ const onKeyDown = (e: KeyboardEvent) => {
36
+ if (e.key === "Escape") {
37
+ const prevIndex = openIndex
38
+ setOpenIndex(null)
39
+ triggerRefs.current.get(prevIndex)?.focus()
40
+ }
41
+ }
42
+
43
+ document.addEventListener("click", onClick)
44
+ document.addEventListener("keydown", onKeyDown)
45
+ return () => {
46
+ document.removeEventListener("click", onClick)
47
+ document.removeEventListener("keydown", onKeyDown)
48
+ }
49
+ }, [openIndex])
50
+
51
+ const handleToggle = (index: number) => {
52
+ setOpenIndex((prev) => (prev === index ? null : index))
53
+ }
54
+
55
+ return (
56
+ <nav
57
+ ref={navRef}
58
+ className="sticky top-0 z-50 border-black/10 border-b bg-white"
59
+ aria-label="Main navigation"
60
+ >
61
+ <div className="mx-auto flex max-w-7xl items-center justify-between px-6 py-4">
62
+ {/* Logo */}
63
+ <Link href="/" className="shrink-0" aria-label="Home">
64
+ {logoUrl ? (
65
+ <Image
66
+ src={logoUrl}
67
+ alt="Basement"
68
+ width={131}
69
+ height={23}
70
+ className="h-8 w-auto"
71
+ />
72
+ ) : (
73
+ <span className="font-semibold text-black text-lg">Basement</span>
74
+ )}
75
+ </Link>
76
+
77
+ {/* Navigation items */}
78
+ {navigationItems.length > 0 ? (
79
+ <ul className="hidden items-center gap-8 lg:flex">
80
+ {navigationItems.map((item, i) => (
81
+ <NavItemRenderer
82
+ key={item._key}
83
+ item={item}
84
+ isOpen={openIndex === i}
85
+ onToggle={() => handleToggle(i)}
86
+ triggerRef={(el) => {
87
+ if (el) {
88
+ triggerRefs.current.set(i, el)
89
+ return
90
+ }
91
+
92
+ triggerRefs.current.delete(i)
93
+ }}
94
+ />
95
+ ))}
96
+ </ul>
97
+ ) : null}
98
+
99
+ {/* CTA buttons */}
100
+ {ctaButtons.length > 0 ? (
101
+ <div className="hidden items-center gap-3 lg:flex">
102
+ {ctaButtons.map((cta) => (
103
+ <CtaButtonRenderer key={cta._key} cta={cta} />
104
+ ))}
105
+ </div>
106
+ ) : null}
107
+ </div>
108
+ </nav>
109
+ )
110
+ }
@@ -0,0 +1,8 @@
1
+ import { getNavbar } from "@/lib/integrations/sanity/fetchers/layout"
2
+
3
+ import { HeaderClient } from "./header-client"
4
+
5
+ export const Header = async () => {
6
+ const navbar = await getNavbar()
7
+ return <HeaderClient data={navbar} />
8
+ }
@@ -0,0 +1,45 @@
1
+ import { stegaClean } from "next-sanity"
2
+ import {
3
+ getCompanyData,
4
+ getNavbar,
5
+ } from "@/lib/integrations/sanity/fetchers/layout"
6
+ import {
7
+ generateOrganizationJsonLd,
8
+ generateWebSiteJsonLd,
9
+ JsonLd as JsonLdBase,
10
+ } from "@/lib/utils/json-ld"
11
+
12
+ const APP_DESCRIPTION =
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
+
15
+ export const JsonLd = async () => {
16
+ const [navbarData, companyData] = await Promise.all([
17
+ getNavbar(),
18
+ getCompanyData(),
19
+ ])
20
+
21
+ const sameAs = companyData?.socialLinks
22
+ ?.map((link) => stegaClean(link.url))
23
+ .filter((url): url is string => Boolean(url))
24
+
25
+ const logoUrl = navbarData?.logo?.asset?.url
26
+
27
+ return (
28
+ <>
29
+ <JsonLdBase
30
+ data={generateWebSiteJsonLd({
31
+ name: "Basement",
32
+ description: APP_DESCRIPTION,
33
+ })}
34
+ />
35
+ <JsonLdBase
36
+ data={generateOrganizationJsonLd({
37
+ name: "Basement",
38
+ ...(logoUrl ? { logo: logoUrl } : {}),
39
+ description: APP_DESCRIPTION,
40
+ ...(sameAs?.length ? { sameAs } : {}),
41
+ })}
42
+ />
43
+ </>
44
+ )
45
+ }
@@ -0,0 +1,30 @@
1
+ import Link from "next/link"
2
+ import { Footer } from "@/components/layout/footer"
3
+ import { Header } from "@/components/layout/header"
4
+ import { cn } from "@/lib/styles/cn"
5
+
6
+ interface WrapperProps extends React.HTMLAttributes<HTMLDivElement> {}
7
+
8
+ const SkipToMainContent = () => (
9
+ <Link
10
+ href="#main-content"
11
+ className="sr-only focus:not-sr-only focus:fixed focus:top-4 focus:left-4 focus:z-9999 focus:bg-black focus:p-2 focus:text-white focus:outline-none focus:ring-2 focus:ring-white"
12
+ >
13
+ Skip to main content
14
+ </Link>
15
+ )
16
+
17
+ export const Wrapper = ({ children, className, ...props }: WrapperProps) => (
18
+ <>
19
+ <SkipToMainContent />
20
+ <Header />
21
+ <main
22
+ id="main-content"
23
+ className={cn("relative flex grow flex-col", className)}
24
+ {...props}
25
+ >
26
+ {children}
27
+ </main>
28
+ <Footer />
29
+ </>
30
+ )
@@ -0,0 +1,83 @@
1
+ import { RichText } from "@/components/sanity/rich-text"
2
+ import { SanityImage } from "@/components/ui/sanity-image"
3
+ import { formatDate } from "@/lib/utils/format-date"
4
+ import {RelatedPostItem} from "./related-post-item"
5
+ import type { ArticleContentProps } from "@/components/page-builder/types"
6
+
7
+
8
+ export function ArticleContent({
9
+ block
10
+ }: ArticleContentProps) {
11
+ const { author, body, categories, date, relatedPosts, thumbnail } = block
12
+ return (
13
+ <div className="py-8 md:py-12">
14
+ <header className="mx-auto max-w-3xl space-y-6">
15
+ {categories && categories.length > 0 ? (
16
+ <div className="flex flex-wrap gap-2">
17
+ {/* @ts-ignore */}
18
+ {categories.map((category) => (
19
+ <span
20
+ key={category._id}
21
+ className="rounded-full border border-current/20 px-3 py-1 text-xs"
22
+ >
23
+ {category.title}
24
+ </span>
25
+ ))}
26
+ </div>
27
+ ) : null}
28
+
29
+ <div className="flex items-center gap-3">
30
+ {author?.avatar ? (
31
+ <SanityImage
32
+ image={author.avatar}
33
+ alt={author.name || ""}
34
+ className="size-10 rounded-full object-cover"
35
+ />
36
+ ) : null}
37
+ <div className="flex flex-col">
38
+ {author?.name ? (
39
+ <span className="font-medium text-sm">{author.name}</span>
40
+ ) : null}
41
+ <div className="flex items-center gap-2 text-current/60 text-sm">
42
+ {author?.role ? <span>{author.role}</span> : null}
43
+ {author?.role && date ? <span aria-hidden>·</span> : null}
44
+ {date ? <time dateTime={date}>{formatDate(date)}</time> : null}
45
+ </div>
46
+ </div>
47
+ </div>
48
+ </header>
49
+
50
+ {thumbnail ? (
51
+ <div className="mx-auto mt-8 max-w-4xl overflow-hidden rounded-lg">
52
+ <SanityImage
53
+ image={thumbnail}
54
+ alt={thumbnail.alt || ""}
55
+ priority
56
+ className="aspect-video w-full object-cover"
57
+ />
58
+ </div>
59
+ ) : null}
60
+
61
+ {body ? (
62
+ <div className="mx-auto mt-10 max-w-3xl space-y-4 text-base/7 text-current/80 md:text-lg/8">
63
+ <RichText content={body} />
64
+ </div>
65
+ ) : null}
66
+
67
+ {relatedPosts && relatedPosts.length > 0 ? (
68
+ <aside className="mx-auto mt-16 max-w-3xl">
69
+ <h2 className="font-semibold text-2xl">Related Articles</h2>
70
+ <ul className="mt-6 grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
71
+ {relatedPosts.map((post) => {
72
+ if (!post.resolvedSlug) return null
73
+
74
+ return (
75
+ <RelatedPostItem key={post._id} post={post} />
76
+ )
77
+ })}
78
+ </ul>
79
+ </aside>
80
+ ) : null}
81
+ </div>
82
+ )
83
+ }
@@ -0,0 +1,27 @@
1
+ import { Link } from "@/components/ui/link"
2
+ import { SanityImage } from "@/components/ui/sanity-image"
3
+ import type { ArticleRelatedPost } from "@/components/page-builder/types"
4
+
5
+ export const RelatedPostItem = ({ post }: {post: ArticleRelatedPost}) => {
6
+ return (
7
+ <li key={post._id}>
8
+ <Link
9
+ href={`/${post.resolvedSlug}`}
10
+ className="group flex flex-col gap-3"
11
+ >
12
+ {post.metadata?.image ? (
13
+ <div className="overflow-hidden rounded-lg">
14
+ <SanityImage
15
+ image={post.metadata.image}
16
+ alt={post.title || ""}
17
+ className="aspect-[16/9] w-full object-cover transition-transform duration-300 group-hover:scale-105"
18
+ />
19
+ </div>
20
+ ) : null}
21
+ <h3 className="text-balance font-medium text-base leading-snug">
22
+ {post.title}
23
+ </h3>
24
+ </Link>
25
+ </li>
26
+ )
27
+ }
@@ -0,0 +1,17 @@
1
+ import type { PageBuilderBlock } from "../types"
2
+
3
+ type DescriptionProps = {
4
+ block: Extract<PageBuilderBlock, { _type: "description" }>
5
+ }
6
+
7
+ export function Description({ block }: DescriptionProps) {
8
+ if (!block.description) return null
9
+
10
+ return (
11
+ <section className="py-4">
12
+ <p className="max-w-[62ch] text-base/7 text-current/72 md:text-lg/8">
13
+ {block.description}
14
+ </p>
15
+ </section>
16
+ )
17
+ }
@@ -0,0 +1,17 @@
1
+ import type { PageBuilderBlock } from "../types"
2
+
3
+ type HeroProps = {
4
+ block: Extract<PageBuilderBlock, { _type: "hero" }>
5
+ }
6
+
7
+ export function Hero({ block }: HeroProps) {
8
+ if (!block.headline) return null
9
+
10
+ return (
11
+ <section className="py-8 md:py-12">
12
+ <h1 className="max-w-4xl font-semibold text-4xl tracking-tight md:text-6xl">
13
+ {block.headline}
14
+ </h1>
15
+ </section>
16
+ )
17
+ }
@@ -0,0 +1,66 @@
1
+ import { Link } from "@/components/ui/link"
2
+ import { SanityImage } from "@/components/ui/sanity-image"
3
+ import { formatDate } from "@/lib/utils/format-date"
4
+ import type { BlogArticleCard } from "./types"
5
+
6
+ type ContentCardProps = {
7
+ article: BlogArticleCard
8
+ className?: string | undefined
9
+ }
10
+
11
+ export const ContentCard = ({ article, className }: ContentCardProps) => {
12
+ const href = article.resolvedSlug ? `/${article.resolvedSlug}` : null
13
+ if (!href) return null
14
+
15
+ const blog = article.blogContent
16
+ const title = article.title ?? ""
17
+ const categories = blog?.categories ?? []
18
+ const authorName = blog?.author?.name
19
+ const publishedAt = blog?.date ?? null
20
+ const formattedDate = publishedAt ? formatDate(publishedAt) : null
21
+ const thumbnail = blog?.thumbnail ?? article.metadata?.image
22
+
23
+ return (
24
+ <li className={className}>
25
+ <Link href={href} className="group flex flex-col gap-3">
26
+ <div className="relative aspect-16/9 w-full overflow-hidden rounded-lg bg-gray-100">
27
+ {thumbnail ? (
28
+ <SanityImage
29
+ image={thumbnail}
30
+ alt=""
31
+ fill
32
+ sizes="(max-width: 768px) 100vw, (max-width: 1024px) 50vw, 33vw"
33
+ className="w-full object-cover transition-transform duration-300 group-hover:scale-105"
34
+ />
35
+ ) : null}
36
+ </div>
37
+
38
+ <div className="flex flex-col gap-2">
39
+ {categories.length > 0 ? (
40
+ <div className="flex flex-wrap gap-1.5">
41
+ {categories.map((category) => (
42
+ <span key={category._id} className="text-current/60 text-xs">
43
+ {category.title}
44
+ </span>
45
+ ))}
46
+ </div>
47
+ ) : null}
48
+
49
+ <h3 className="text-balance font-medium text-lg leading-snug">
50
+ {title}
51
+ </h3>
52
+
53
+ {authorName || formattedDate ? (
54
+ <div className="flex items-center gap-2 text-current/60 text-sm">
55
+ {authorName ? <span>{authorName}</span> : null}
56
+ {authorName && formattedDate ? <span aria-hidden>·</span> : null}
57
+ {publishedAt && formattedDate ? (
58
+ <time dateTime={publishedAt}>{formattedDate}</time>
59
+ ) : null}
60
+ </div>
61
+ ) : null}
62
+ </div>
63
+ </Link>
64
+ </li>
65
+ )
66
+ }
@@ -0,0 +1,42 @@
1
+ import type { BlogArticleCard } from "./types"
2
+ import { ContentCard } from "./content-card"
3
+
4
+ type ContentGridProps = {
5
+ articles: BlogArticleCard[]
6
+ start?: number
7
+ end?: number
8
+ className?: string
9
+ }
10
+
11
+ export function ContentGrid({
12
+ articles,
13
+ start = 0,
14
+ end,
15
+ className,
16
+ }: ContentGridProps) {
17
+ const slice = articles.slice(start, end)
18
+
19
+ if (slice.length === 0) {
20
+ if (start === 0) {
21
+ return <p className="text-current/70 text-sm">No articles found.</p>
22
+ }
23
+ return null
24
+ }
25
+
26
+ return (
27
+ <ul
28
+ className={
29
+ className ?? "grid grid-cols-2 gap-8 md:grid-cols-2 lg:grid-cols-3"
30
+ }
31
+ >
32
+ {slice.map((article) => {
33
+ const slug = article.resolvedSlug
34
+ if (!slug) return null
35
+
36
+ return (
37
+ <ContentCard key={article._id} article={article} />
38
+ )
39
+ })}
40
+ </ul>
41
+ )
42
+ }