bsmnt 0.2.11 → 0.3.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 (115) 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 +3 -14
  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/package.json +1 -1
  15. package/src/templates/next-pagebuilder/.env.example +11 -0
  16. package/src/templates/next-pagebuilder/README.md +23 -0
  17. package/src/templates/next-pagebuilder/_gitignore +67 -0
  18. package/src/templates/next-pagebuilder/app/(content)/[[...slug]]/page.tsx +68 -0
  19. package/src/templates/next-pagebuilder/app/(content)/layout.tsx +13 -0
  20. package/src/templates/next-pagebuilder/app/api/[[...slug]]/route.ts +100 -0
  21. package/src/templates/next-pagebuilder/app/api/draft-mode/disable/route.ts +7 -0
  22. package/src/templates/next-pagebuilder/app/api/draft-mode/enable/route.ts +20 -0
  23. package/src/templates/next-pagebuilder/app/api/revalidate/route.ts +121 -0
  24. package/src/templates/next-pagebuilder/app/favicon.ico +0 -0
  25. package/src/templates/next-pagebuilder/app/layout.tsx +80 -0
  26. package/src/templates/next-pagebuilder/app/robots.ts +15 -0
  27. package/src/templates/next-pagebuilder/app/sitemap.md/route.ts +124 -0
  28. package/src/templates/next-pagebuilder/app/sitemap.xml/route.ts +80 -0
  29. package/src/templates/next-pagebuilder/app/studio/[[...tool]]/page.tsx +8 -0
  30. package/src/templates/next-pagebuilder/biome.json +239 -0
  31. package/src/templates/next-pagebuilder/components/layout/footer/index.tsx +95 -0
  32. package/src/templates/next-pagebuilder/components/layout/header/components/cta-button.tsx +28 -0
  33. package/src/templates/next-pagebuilder/components/layout/header/components/mega-menu-panel.tsx +90 -0
  34. package/src/templates/next-pagebuilder/components/layout/header/components/nav-item-renderer.tsx +98 -0
  35. package/src/templates/next-pagebuilder/components/layout/header/components/nav-leaf-item.tsx +33 -0
  36. package/src/templates/next-pagebuilder/components/layout/header/components/types.ts +7 -0
  37. package/src/templates/next-pagebuilder/components/layout/header/header-client.tsx +110 -0
  38. package/src/templates/next-pagebuilder/components/layout/header/index.tsx +8 -0
  39. package/src/templates/next-pagebuilder/components/layout/json-ld/index.tsx +45 -0
  40. package/src/templates/next-pagebuilder/components/layout/wrapper/index.tsx +30 -0
  41. package/src/templates/next-pagebuilder/components/page-builder/components/article-content/index.tsx +83 -0
  42. package/src/templates/next-pagebuilder/components/page-builder/components/article-content/related-post-item.tsx +27 -0
  43. package/src/templates/next-pagebuilder/components/page-builder/components/description.tsx +17 -0
  44. package/src/templates/next-pagebuilder/components/page-builder/components/hero.tsx +17 -0
  45. package/src/templates/next-pagebuilder/components/page-builder/components/post-collection/content-card.tsx +66 -0
  46. package/src/templates/next-pagebuilder/components/page-builder/components/post-collection/content-grid.tsx +42 -0
  47. package/src/templates/next-pagebuilder/components/page-builder/components/post-collection/index.tsx +28 -0
  48. package/src/templates/next-pagebuilder/components/page-builder/components/post-collection/types.ts +16 -0
  49. package/src/templates/next-pagebuilder/components/page-builder/renderer.tsx +36 -0
  50. package/src/templates/next-pagebuilder/components/page-builder/types.ts +23 -0
  51. package/src/templates/next-pagebuilder/components/page-document/index.tsx +91 -0
  52. package/src/templates/next-pagebuilder/components/sanity/draft-mode-toggle.tsx +27 -0
  53. package/src/templates/next-pagebuilder/components/sanity/rich-text.tsx +87 -0
  54. package/src/templates/next-pagebuilder/components/sanity/visual-editing.tsx +27 -0
  55. package/src/templates/next-pagebuilder/components/ui/image/index.tsx +216 -0
  56. package/src/templates/next-pagebuilder/components/ui/link/index.tsx +152 -0
  57. package/src/templates/next-pagebuilder/components/ui/sanity-image/index.tsx +41 -0
  58. package/src/templates/next-pagebuilder/lib/integrations/check-integration.ts +5 -0
  59. package/src/templates/next-pagebuilder/lib/integrations/sanity/client.ts +27 -0
  60. package/src/templates/next-pagebuilder/lib/integrations/sanity/components/disable-draft-mode.tsx +23 -0
  61. package/src/templates/next-pagebuilder/lib/integrations/sanity/components/page-builder-input.tsx +36 -0
  62. package/src/templates/next-pagebuilder/lib/integrations/sanity/components/page-category-input.tsx +50 -0
  63. package/src/templates/next-pagebuilder/lib/integrations/sanity/components/rich-text.tsx +84 -0
  64. package/src/templates/next-pagebuilder/lib/integrations/sanity/confirm-publish-action.ts +40 -0
  65. package/src/templates/next-pagebuilder/lib/integrations/sanity/env.ts +34 -0
  66. package/src/templates/next-pagebuilder/lib/integrations/sanity/fetchers/layout.ts +35 -0
  67. package/src/templates/next-pagebuilder/lib/integrations/sanity/icons.ts +58 -0
  68. package/src/templates/next-pagebuilder/lib/integrations/sanity/live/index.tsx +61 -0
  69. package/src/templates/next-pagebuilder/lib/integrations/sanity/markdown-proxy.config.ts +50 -0
  70. package/src/templates/next-pagebuilder/lib/integrations/sanity/page-builder-config.ts +132 -0
  71. package/src/templates/next-pagebuilder/lib/integrations/sanity/page-category.ts +28 -0
  72. package/src/templates/next-pagebuilder/lib/integrations/sanity/queries.ts +281 -0
  73. package/src/templates/next-pagebuilder/lib/integrations/sanity/sanity.cli.ts +29 -0
  74. package/src/templates/next-pagebuilder/lib/integrations/sanity/sanity.config.ts +211 -0
  75. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/components/index.ts +4 -0
  76. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/components/reusable/blog-content.ts +89 -0
  77. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/components/reusable/description.ts +29 -0
  78. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/components/reusable/hero.ts +28 -0
  79. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/components/singleton/content-collection.ts +45 -0
  80. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/content/author.ts +70 -0
  81. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/content/blog-category.ts +55 -0
  82. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/index.ts +96 -0
  83. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/layout/company-data.ts +62 -0
  84. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/layout/footer.ts +79 -0
  85. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/layout/navbar.ts +74 -0
  86. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/link.ts +125 -0
  87. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/logo-field.ts +9 -0
  88. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/metadata.ts +68 -0
  89. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/nav-objects.ts +192 -0
  90. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/page-builder.ts +39 -0
  91. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/page-folder.ts +124 -0
  92. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/page.ts +232 -0
  93. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/richText.ts +63 -0
  94. package/src/templates/next-pagebuilder/lib/integrations/sanity/singletons.ts +44 -0
  95. package/src/templates/next-pagebuilder/lib/integrations/sanity/structure.ts +453 -0
  96. package/src/templates/next-pagebuilder/lib/integrations/sanity/utils/image.ts +8 -0
  97. package/src/templates/next-pagebuilder/lib/integrations/sanity/utils/link.ts +137 -0
  98. package/src/templates/next-pagebuilder/lib/integrations/sanity/utils/page-builder-markdown.ts +81 -0
  99. package/src/templates/next-pagebuilder/lib/scripts/sanity-typegen.ts +45 -0
  100. package/src/templates/next-pagebuilder/lib/styles/cn.ts +5 -0
  101. package/src/templates/next-pagebuilder/lib/styles/global.css +70 -0
  102. package/src/templates/next-pagebuilder/lib/utils/base-url.ts +17 -0
  103. package/src/templates/next-pagebuilder/lib/utils/format-date.ts +8 -0
  104. package/src/templates/next-pagebuilder/lib/utils/json-ld.tsx +213 -0
  105. package/src/templates/next-pagebuilder/lib/utils/metadata.ts +167 -0
  106. package/src/templates/next-pagebuilder/lib/utils/sitemap.ts +37 -0
  107. package/src/templates/next-pagebuilder/lib/utils/slug-tag.ts +6 -0
  108. package/src/templates/next-pagebuilder/next.config.ts +134 -0
  109. package/src/templates/next-pagebuilder/package.json +71 -0
  110. package/src/templates/next-pagebuilder/postcss.config.mjs +39 -0
  111. package/src/templates/next-pagebuilder/proxy.ts +81 -0
  112. package/src/templates/next-pagebuilder/svg.d.ts +5 -0
  113. package/src/templates/next-pagebuilder/tsconfig.json +38 -0
  114. package/src/helpers/integrate/sanity/files/lib/scripts/copy-sanity-mcp.ts +0 -23
  115. package/src/helpers/integrate/sanity/files/lib/scripts/generate-page.ts +0 -297
@@ -0,0 +1,152 @@
1
+ "use client"
2
+
3
+ import NextLink from "next/link"
4
+ import { usePathname } from "next/navigation"
5
+ import {
6
+ type AnchorHTMLAttributes,
7
+ type ComponentProps,
8
+ type MouseEvent,
9
+ useEffect,
10
+ useState,
11
+ } from "react"
12
+
13
+ // Helper to extract props safe for button elements
14
+ function getButtonProps(props: Record<string, unknown>) {
15
+ const {
16
+ href,
17
+ target,
18
+ rel,
19
+ "data-external": _dataExternal,
20
+ ...buttonProps
21
+ } = props
22
+ return buttonProps
23
+ }
24
+
25
+ // Helper to extract props safe for div elements
26
+ function getDivProps(props: Record<string, unknown>) {
27
+ const {
28
+ href,
29
+ target,
30
+ rel,
31
+ onClick,
32
+ "data-external": _dataExternal,
33
+ ...divProps
34
+ } = props
35
+ return divProps
36
+ }
37
+
38
+ type CustomLinkProps = Omit<
39
+ AnchorHTMLAttributes<HTMLAnchorElement>,
40
+ keyof ComponentProps<typeof NextLink> | "href"
41
+ > &
42
+ Omit<ComponentProps<typeof NextLink>, "href"> & {
43
+ href?: string
44
+ onClick?: (e: MouseEvent<HTMLElement>) => void
45
+ scroll?: boolean
46
+ }
47
+
48
+ export function Link({
49
+ href,
50
+ children,
51
+ onClick,
52
+ scroll = false, // Default to false to prevent scroll restoration warnings with fixed/sticky elements
53
+ ...props
54
+ }: CustomLinkProps) {
55
+ const [shouldPrefetch, setShouldPrefetch] = useState(false)
56
+ const [isExternal, setIsExternal] = useState(false)
57
+ const [isActive, setIsActive] = useState(false)
58
+
59
+ // Get pathname - deferred to avoid blocking static generation
60
+ // usePathname is safe to call but we defer the active check to useEffect
61
+ const pathname = usePathname()
62
+
63
+ useEffect(() => {
64
+ // Check if this link is active (current page)
65
+ if (href && pathname) {
66
+ setIsActive(pathname === href)
67
+ }
68
+ }, [href, pathname])
69
+
70
+ useEffect(() => {
71
+ // Skip if no href
72
+ if (!href) return
73
+
74
+ // Check if external link
75
+ try {
76
+ const url = new URL(href, window.location.href)
77
+ setIsExternal(url.host !== window.location.host)
78
+ } catch {
79
+ setIsExternal(false)
80
+ }
81
+
82
+ // Only prefetch on good connections
83
+ const connection = (
84
+ navigator as Navigator & {
85
+ connection?: { effectiveType: string; saveData: boolean }
86
+ }
87
+ ).connection
88
+ if (connection) {
89
+ const { effectiveType, saveData } = connection
90
+ setShouldPrefetch(effectiveType === "4g" && !saveData)
91
+ } else {
92
+ // Default to prefetching if API not available
93
+ setShouldPrefetch(true)
94
+ }
95
+ }, [href])
96
+
97
+ // If no href is provided but there's an onClick, render a button
98
+ if (!href && onClick) {
99
+ return (
100
+ <button
101
+ onClick={(e: MouseEvent<HTMLButtonElement>) => onClick(e)}
102
+ type="button"
103
+ {...getButtonProps(props)}
104
+ >
105
+ {children}
106
+ </button>
107
+ )
108
+ }
109
+
110
+ // If no href and no onClick, render a div
111
+ if (!href) {
112
+ return <div {...getDivProps(props)}>{children}</div>
113
+ }
114
+
115
+ // Block dangerous URIs (javascript:, data:, vbscript:)
116
+ const isDangerousHref = /^(javascript|data|vbscript):/i.test(href)
117
+ if (isDangerousHref) {
118
+ return <span {...getDivProps(props)}>{children}</span>
119
+ }
120
+
121
+ // For SSR, check if it's external based on the href pattern
122
+ const isExternalSSR =
123
+ href.startsWith("http://") || href.startsWith("https://")
124
+
125
+ if (isExternalSSR || isExternal) {
126
+ return (
127
+ <a
128
+ href={href}
129
+ target="_blank"
130
+ rel="noopener noreferrer"
131
+ data-external
132
+ onClick={onClick}
133
+ {...props}
134
+ >
135
+ {children}
136
+ </a>
137
+ )
138
+ }
139
+
140
+ return (
141
+ <NextLink
142
+ href={href as ComponentProps<typeof NextLink>["href"]}
143
+ prefetch={shouldPrefetch}
144
+ scroll={scroll}
145
+ data-active={isActive || undefined}
146
+ {...(onClick && { onClick })}
147
+ {...props}
148
+ >
149
+ {children}
150
+ </NextLink>
151
+ )
152
+ }
@@ -0,0 +1,41 @@
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?: object
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
+ const { width, height } = getImageDimensions(image.asset)
28
+ const aspectRatio = width / height
29
+
30
+ const imageProps = fill ? { fill: true } : { width, height }
31
+
32
+ return (
33
+ <Image
34
+ src={urlForImage(image).width(maxWidth).url()}
35
+ alt={alt || image.alt || ""}
36
+ aspectRatio={aspectRatio}
37
+ {...imageProps}
38
+ {...props}
39
+ />
40
+ )
41
+ }
@@ -0,0 +1,5 @@
1
+ export const isSanityConfigured = () =>
2
+ Boolean(
3
+ process.env.NEXT_PUBLIC_SANITY_PROJECT_ID &&
4
+ process.env.NEXT_PUBLIC_SANITY_DATASET
5
+ )
@@ -0,0 +1,27 @@
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
@@ -0,0 +1,23 @@
1
+ "use client"
2
+
3
+ import Link from "next/link"
4
+ import { usePathname } from "next/navigation"
5
+ import { useDraftModeEnvironment } from "next-sanity/hooks"
6
+
7
+ export function DisableDraftMode() {
8
+ const environment = useDraftModeEnvironment()
9
+ const pathname = usePathname()
10
+
11
+ if (environment !== "live" && environment !== "unknown") 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,36 @@
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((member) => member.name)
17
+ )
18
+
19
+ return props.renderDefault({
20
+ ...props,
21
+ schemaType: {
22
+ ...props.schemaType,
23
+ of: props.schemaType.of.filter(
24
+ (member) =>
25
+ typeof member.name === "string" && visibleMemberNames.has(member.name)
26
+ ),
27
+ options: {
28
+ ...props.schemaType.options,
29
+ insertMenu: {
30
+ ...props.schemaType.options?.insertMenu,
31
+ groups: getVisiblePageBuilderInsertMenuGroups(pageType),
32
+ },
33
+ },
34
+ },
35
+ })
36
+ }
@@ -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
+ }
@@ -0,0 +1,84 @@
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 { getLinkAttributes, urlForReference } from "../utils/link"
5
+
6
+ interface RichTextProps {
7
+ content: PortableTextProps["value"]
8
+ }
9
+
10
+ type LinkMarkProps = {
11
+ children: React.ReactNode
12
+ value?: unknown
13
+ }
14
+
15
+ const LinkMark = ({ children, value }: LinkMarkProps) => {
16
+ const href = urlForReference(value as Parameters<typeof urlForReference>[0])
17
+ const attrs = getLinkAttributes(
18
+ value as Parameters<typeof getLinkAttributes>[0]
19
+ )
20
+
21
+ return (
22
+ <Link
23
+ href={href}
24
+ target={attrs.target}
25
+ rel={attrs.rel}
26
+ data-sanity-edit-target
27
+ >
28
+ {children}
29
+ </Link>
30
+ )
31
+ }
32
+
33
+ export const RichText = ({ content }: RichTextProps) => {
34
+ if (!content) return null
35
+
36
+ return (
37
+ <PortableText
38
+ value={content}
39
+ components={{
40
+ types: {
41
+ image: ({ value }) => <SanityImage image={value} maxWidth={1920} />,
42
+ table: ({ value }) => {
43
+ const rows = value?.rows as
44
+ | Array<{ _key?: string; cells?: string[] }>
45
+ | undefined
46
+ if (!rows?.length) return null
47
+ return (
48
+ <table>
49
+ <tbody>
50
+ {rows.map((row, i) => (
51
+ <tr key={row._key ?? `row-${i}`}>
52
+ {row.cells?.map((cell) => {
53
+ const cellKey = `${row._key}-${cell}`
54
+ return i === 0 ? (
55
+ <th key={cellKey}>{cell}</th>
56
+ ) : (
57
+ <td key={cellKey}>{cell}</td>
58
+ )
59
+ })}
60
+ </tr>
61
+ ))}
62
+ </tbody>
63
+ </table>
64
+ )
65
+ },
66
+ },
67
+ marks: {
68
+ link: LinkMark,
69
+ pageReference: LinkMark,
70
+ externalLink: LinkMark,
71
+ },
72
+ block: {
73
+ h1: ({ children }) => <h1 className="h1">{children}</h1>,
74
+ h2: ({ children }) => <h2 className="h2">{children}</h2>,
75
+ h3: ({ children }) => <h3 className="h3">{children}</h3>,
76
+ h4: ({ children }) => <h4 className="h4">{children}</h4>,
77
+ h5: ({ children }) => <h5 className="h5">{children}</h5>,
78
+ h6: ({ children }) => <h6 className="h6">{children}</h6>,
79
+ normal: ({ children }) => <p className="p">{children}</p>,
80
+ },
81
+ }}
82
+ />
83
+ )
84
+ }
@@ -0,0 +1,40 @@
1
+ import { useState } from "react";
2
+ import type {
3
+ DocumentActionComponent,
4
+ DocumentActionDialogProps,
5
+ DocumentActionProps,
6
+ } from "sanity";
7
+
8
+ export function createConfirmPublishAction(
9
+ originalPublishAction: DocumentActionComponent,
10
+ ): DocumentActionComponent {
11
+ const ConfirmPublishAction = (props: DocumentActionProps) => {
12
+ const original = originalPublishAction(props);
13
+ const [dialogOpen, setDialogOpen] = useState(false);
14
+
15
+ if (!original) return null;
16
+
17
+ const { dialog: _originalDialog, ...originalWithoutDialog } = original;
18
+ const dialog: DocumentActionDialogProps | false = dialogOpen
19
+ ? {
20
+ type: "confirm",
21
+ tone: "critical",
22
+ message:
23
+ "This will make these changes live on the production site immediately. Continue?",
24
+ onCancel: () => setDialogOpen(false),
25
+ onConfirm: () => {
26
+ setDialogOpen(false);
27
+ original.onHandle?.();
28
+ },
29
+ }
30
+ : false;
31
+
32
+ return {
33
+ ...originalWithoutDialog,
34
+ onHandle: () => setDialogOpen(true),
35
+ dialog,
36
+ };
37
+ };
38
+
39
+ return ConfirmPublishAction;
40
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Sanity Environment Configuration
3
+ *
4
+ * All values have safe defaults to allow the app to build and run
5
+ * without Sanity configured. Use isSanityConfigured() to check
6
+ * if Sanity is properly set up before making API calls.
7
+ */
8
+
9
+ /** Sanity API version - defaults to latest stable */
10
+ export const apiVersion =
11
+ process.env.NEXT_PUBLIC_SANITY_API_VERSION || "2024-03-15"
12
+
13
+ /** Sanity dataset name - defaults to 'production' */
14
+ export const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET || "production"
15
+
16
+ /** Sanity project ID - empty string if not configured */
17
+ export const projectId =
18
+ process.env.NEXT_PUBLIC_SANITY_PROJECT_ID ||
19
+ process.env.SANITY_STUDIO_PROJECT_ID ||
20
+ ""
21
+
22
+ /** Sanity Studio URL for visual editing */
23
+ export const studioUrl =
24
+ process.env.NODE_ENV === "development"
25
+ ? "http://localhost:3000/studio"
26
+ : `${process.env.NEXT_PUBLIC_BASE_URL || ""}/studio`
27
+
28
+ export const sanityToken = process.env.SANITY_API_READ_TOKEN || ""
29
+
30
+ /** Preview URL for draft mode */
31
+ export const previewURL =
32
+ process.env.NODE_ENV === "development"
33
+ ? "http://localhost:3000"
34
+ : process.env.NEXT_PUBLIC_BASE_URL || ""
@@ -0,0 +1,35 @@
1
+ import { sanityFetch } from "@/lib/integrations/sanity/live"
2
+ import {
3
+ COMPANY_DATA_QUERY,
4
+ FOOTER_QUERY,
5
+ NAVBAR_QUERY,
6
+ } from "@/lib/integrations/sanity/queries"
7
+ import type {
8
+ COMPANY_DATA_QUERY_RESULT,
9
+ FOOTER_QUERY_RESULT,
10
+ NAVBAR_QUERY_RESULT,
11
+ } from "@/lib/integrations/sanity/sanity.types"
12
+
13
+ export type NavbarData = NonNullable<NAVBAR_QUERY_RESULT>
14
+ export type FooterData = NonNullable<FOOTER_QUERY_RESULT>
15
+ export type CompanyData = NonNullable<COMPANY_DATA_QUERY_RESULT>
16
+
17
+ export const SANITY_LAYOUT_TAGS = ["navbar", "footer", "companyData"]
18
+
19
+ export const getNavbar = async (): Promise<NavbarData | null> => {
20
+ const { data } = await sanityFetch({ query: NAVBAR_QUERY, tags: ["navbar"] })
21
+ return data
22
+ }
23
+
24
+ export const getFooter = async (): Promise<FooterData | null> => {
25
+ const { data } = await sanityFetch({ query: FOOTER_QUERY, tags: ["footer"] })
26
+ return data
27
+ }
28
+
29
+ export const getCompanyData = async (): Promise<CompanyData | null> => {
30
+ const { data } = await sanityFetch({
31
+ query: COMPANY_DATA_QUERY,
32
+ tags: ["companyData"],
33
+ })
34
+ return data
35
+ }
@@ -0,0 +1,58 @@
1
+ import type { Icon, IconProps } from "@phosphor-icons/react"
2
+ import {
3
+ ArticleNyTimesIcon,
4
+ BrowsersIcon,
5
+ BuildingsIcon,
6
+ FileTextIcon,
7
+ FolderOpenIcon,
8
+ FootprintsIcon,
9
+ LayoutIcon,
10
+ ListBulletsIcon,
11
+ ListIcon,
12
+ PackageIcon,
13
+ RocketIcon,
14
+ ShapesIcon,
15
+ ShareNetworkIcon,
16
+ TagIcon,
17
+ TextAlignLeftIcon,
18
+ UserCircleIcon,
19
+ } from "@phosphor-icons/react/dist/ssr"
20
+ import { createElement, forwardRef } from "react"
21
+
22
+ export type SanityStudioIcon = Icon
23
+
24
+ const asDuotoneIcon = (IconComponent: Icon): SanityStudioIcon => {
25
+ const DuotoneIcon = forwardRef<SVGSVGElement, IconProps>((props, ref) =>
26
+ createElement(IconComponent, { ...props, ref, weight: "duotone" })
27
+ )
28
+
29
+ DuotoneIcon.displayName = `${IconComponent.displayName ?? "Phosphor"}Duotone`
30
+
31
+ return DuotoneIcon
32
+ }
33
+
34
+ export const pageBuilderCardIcon = asDuotoneIcon(PackageIcon)
35
+ export const pageBuilderCardListIcon = asDuotoneIcon(ListIcon)
36
+
37
+ export const authorIcon = asDuotoneIcon(UserCircleIcon)
38
+
39
+ export const pageListIcon = asDuotoneIcon(ListBulletsIcon)
40
+
41
+ export const blogCategoryIcon = asDuotoneIcon(TagIcon)
42
+
43
+ export const componentsFolderIcon = asDuotoneIcon(ShapesIcon)
44
+
45
+ export const companyDataIcon = asDuotoneIcon(BuildingsIcon)
46
+ export const socialLinksIcon = asDuotoneIcon(ShareNetworkIcon)
47
+ export const navbarIcon = asDuotoneIcon(ListIcon)
48
+ export const footerIcon = asDuotoneIcon(FootprintsIcon)
49
+ export const layoutFolderIcon = asDuotoneIcon(LayoutIcon)
50
+
51
+ export const heroIcon = asDuotoneIcon(RocketIcon)
52
+ export const descriptionIcon = asDuotoneIcon(TextAlignLeftIcon)
53
+ export const contentIcon = asDuotoneIcon(ArticleNyTimesIcon)
54
+
55
+ export const pageIcon = asDuotoneIcon(FileTextIcon)
56
+ export const pagesFolderIcon = asDuotoneIcon(BrowsersIcon)
57
+ export const pageFolderIcon = asDuotoneIcon(FolderOpenIcon)
58
+ export const contentFolderIcon = asDuotoneIcon(FolderOpenIcon)
@@ -0,0 +1,61 @@
1
+ import { NextResponse } from "next/server"
2
+ import { defineLive } from "next-sanity/live"
3
+ import { isSanityConfigured } from "@/lib/integrations/check-integration"
4
+ import { client } from "../client"
5
+ import { sanityToken } from "../env"
6
+
7
+ const isConfigured = isSanityConfigured() && client
8
+
9
+ const liveExports =
10
+ isConfigured && client
11
+ ? defineLive({
12
+ client,
13
+ browserToken: sanityToken,
14
+ serverToken: sanityToken,
15
+ })
16
+ : null
17
+
18
+ export const sanityFetch =
19
+ liveExports?.sanityFetch ?? (async () => ({ data: null }))
20
+
21
+ export const SanityLive = liveExports?.SanityLive ?? (() => null)
22
+
23
+ export const SanityFetch = async <T,>(
24
+ slug: string,
25
+ query: string,
26
+ label = "Content"
27
+ ): Promise<T | NextResponse> => {
28
+ if (!client) {
29
+ return new NextResponse(`# 503 Unavailable\n\nCMS client unavailable.`, {
30
+ status: 503,
31
+ headers: { "Content-Type": "text/markdown; charset=utf-8" },
32
+ })
33
+ }
34
+
35
+ try {
36
+ const { data } = await sanityFetch({
37
+ query,
38
+ params: { slug },
39
+ })
40
+
41
+ const result = data as T | null
42
+
43
+ if (!result) {
44
+ return new NextResponse(`# 404 Not Found\n\nPage not found.`, {
45
+ status: 404,
46
+ headers: { "Content-Type": "text/markdown; charset=utf-8" },
47
+ })
48
+ }
49
+
50
+ return result
51
+ } catch (error) {
52
+ console.error(`Error fetching ${label.toLowerCase()}:`, error)
53
+ return new NextResponse(
54
+ `# 500 Error\n\nFailed to fetch ${label.toLowerCase()}.`,
55
+ {
56
+ status: 500,
57
+ headers: { "Content-Type": "text/markdown; charset=utf-8" },
58
+ }
59
+ )
60
+ }
61
+ }