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,211 @@
1
+ import { table } from "@sanity/table"
2
+ import { defineConfig } from "sanity"
3
+ import {
4
+ defineDocuments,
5
+ defineLocations,
6
+ presentationTool,
7
+ } from "sanity/presentation"
8
+ import { structureTool } from "sanity/structure"
9
+ import { media } from "sanity-plugin-media"
10
+ import { createConfirmPublishAction } from "./confirm-publish-action"
11
+ import { dataset, previewURL, projectId } from "./env"
12
+ import { schema } from "./schemas"
13
+ import { singletonComponentTypes } from "./singletons"
14
+ import { structure } from "./structure"
15
+
16
+ const isProd = process.env.NODE_ENV === "production"
17
+
18
+ const singletonDocumentActions = new Set([
19
+ "publish",
20
+ "discardChanges",
21
+ "restore",
22
+ ])
23
+
24
+ const isSingletonDocument = (schemaType: string, documentId?: string) =>
25
+ singletonComponentTypes.has(schemaType) ||
26
+ (schemaType === "page" && documentId === "page-homepage")
27
+
28
+ const MAX_FOLDER_DEPTH = 6
29
+
30
+ const buildNestedSlugExpression = (
31
+ parentReferenceField: string,
32
+ leafSlugField: string
33
+ ) => {
34
+ const cases = Array.from({ length: MAX_FOLDER_DEPTH }, (_, index) => {
35
+ const depth = MAX_FOLDER_DEPTH - index
36
+ const parentSlugRefs = Array.from({ length: depth }, (_, parentIndex) => {
37
+ const remainingDepth = depth - parentIndex - 1
38
+ return `${parentReferenceField}${"->parentFolder".repeat(remainingDepth)}->slug.current`
39
+ })
40
+
41
+ const pathDefinedChecks = parentSlugRefs
42
+ .map((field) => `defined(${field})`)
43
+ .join(" && ")
44
+
45
+ const childPath = [...parentSlugRefs, leafSlugField].join(' + "/" + ')
46
+ const folderRootPath = parentSlugRefs.join(' + "/" + ')
47
+
48
+ return [
49
+ `${pathDefinedChecks} && ${leafSlugField} == "/" => ${folderRootPath}`,
50
+ `${pathDefinedChecks} && defined(${leafSlugField}) => ${childPath}`,
51
+ ]
52
+ }).flat()
53
+
54
+ return `
55
+ select(
56
+ ${cases.join(",\n ")},
57
+ ${leafSlugField}
58
+ )
59
+ `
60
+ }
61
+
62
+ const pageResolvedSlugExpression = buildNestedSlugExpression(
63
+ "pageFolder",
64
+ "slug.current"
65
+ )
66
+
67
+ export default defineConfig({
68
+ basePath: "/studio",
69
+ projectId,
70
+ dataset,
71
+ schema: {
72
+ ...schema,
73
+ templates: (previousTemplates) => [
74
+ ...previousTemplates.filter(
75
+ (template) =>
76
+ ![
77
+ "page-homepage",
78
+ "page-with-folder",
79
+ "page-folder-with-parent",
80
+ ].includes(template.id)
81
+ ),
82
+ {
83
+ id: "page-homepage",
84
+ title: "Homepage",
85
+ schemaType: "page",
86
+ value: {
87
+ title: "Homepage",
88
+ },
89
+ },
90
+ {
91
+ id: "page-with-folder",
92
+ title: "Page in Folder",
93
+ schemaType: "page",
94
+ parameters: [
95
+ { name: "pageFolderId", title: "Page Folder ID", type: "string" },
96
+ ],
97
+ value: ({ pageFolderId }: { pageFolderId?: string }) => ({
98
+ pageFolder: pageFolderId
99
+ ? {
100
+ _type: "reference",
101
+ _ref: pageFolderId,
102
+ }
103
+ : undefined,
104
+ }),
105
+ },
106
+ {
107
+ id: "page-folder-with-parent",
108
+ title: "Page Folder in Folder",
109
+ schemaType: "pageFolder",
110
+ parameters: [
111
+ { name: "parentFolderId", title: "Parent Folder ID", type: "string" },
112
+ ],
113
+ value: ({ parentFolderId }: { parentFolderId?: string }) => ({
114
+ parentFolder: parentFolderId
115
+ ? {
116
+ _type: "reference",
117
+ _ref: parentFolderId,
118
+ }
119
+ : undefined,
120
+ }),
121
+ },
122
+ ],
123
+ },
124
+ document: {
125
+ actions: (previousActions, context) => {
126
+ const filtered = isSingletonDocument(
127
+ context.schemaType,
128
+ context.documentId
129
+ )
130
+ ? previousActions.filter(
131
+ (action) =>
132
+ action.action !== undefined &&
133
+ singletonDocumentActions.has(action.action)
134
+ )
135
+ : previousActions
136
+
137
+ if (!isProd) return filtered
138
+
139
+ return filtered.map((action) =>
140
+ action.action === "publish"
141
+ ? createConfirmPublishAction(action)
142
+ : action
143
+ )
144
+ },
145
+ newDocumentOptions: (previousOptions) =>
146
+ previousOptions.filter(
147
+ (option) =>
148
+ !singletonComponentTypes.has(option.templateId) &&
149
+ option.templateId !== "page-homepage"
150
+ ),
151
+ },
152
+ plugins: [
153
+ structureTool({ structure }),
154
+
155
+ presentationTool({
156
+ name: "preview",
157
+ title: "Preview",
158
+ resolve: {
159
+ // Map routes to documents and GROQ filters
160
+ mainDocuments: defineDocuments([
161
+ {
162
+ route: "/",
163
+ filter: '_type == "page" && _id == "page-homepage"',
164
+ },
165
+ // Page builder pages - all other slugs (supports nested paths)
166
+ {
167
+ route: "/:slug+",
168
+ filter: `_type == "page" && ${pageResolvedSlugExpression} == $slug`,
169
+ },
170
+ ]),
171
+ locations: {
172
+ page: defineLocations({
173
+ select: {
174
+ _id: "_id",
175
+ title: "title",
176
+ slug: "slug.current",
177
+ resolvedSlug: pageResolvedSlugExpression,
178
+ },
179
+ resolve: (doc) => {
180
+ if (doc?._id === "page-homepage") {
181
+ return {
182
+ locations: [{ title: doc?.title || "Homepage", href: "/" }],
183
+ }
184
+ }
185
+ if (doc?.resolvedSlug) {
186
+ return {
187
+ locations: [
188
+ {
189
+ title: doc?.title || "Page",
190
+ href: `/${doc.resolvedSlug}`,
191
+ },
192
+ ],
193
+ }
194
+ }
195
+ return { locations: [] }
196
+ },
197
+ }),
198
+ },
199
+ },
200
+ previewUrl: {
201
+ origin: previewURL,
202
+ draftMode: {
203
+ enable: "/api/draft-mode/enable",
204
+ disable: "/api/draft-mode/disable",
205
+ },
206
+ },
207
+ }),
208
+ media(),
209
+ table(),
210
+ ],
211
+ })
@@ -0,0 +1,4 @@
1
+ export { blogContent } from "./reusable/blog-content"
2
+ export { description } from "./reusable/description"
3
+ export { hero } from "./reusable/hero"
4
+ export { blogCollection } from "./singleton/content-collection"
@@ -0,0 +1,89 @@
1
+ import { defineField, defineType } from "sanity"
2
+ import { contentIcon } from "@/lib/integrations/sanity/icons"
3
+
4
+ export const blogContent = defineType({
5
+ name: "blogContent",
6
+ title: "Blog Content",
7
+ type: "document",
8
+ icon: contentIcon,
9
+ fields: [
10
+ defineField({
11
+ name: "title",
12
+ title: "Title",
13
+ type: "string",
14
+ validation: (Rule) => Rule.required(),
15
+ }),
16
+ defineField({
17
+ name: "thumbnail",
18
+ title: "Thumbnail",
19
+ type: "image",
20
+ description: "Featured image for the article",
21
+ options: { hotspot: true },
22
+ fields: [
23
+ {
24
+ name: "alt",
25
+ title: "Alt Text",
26
+ type: "string",
27
+ description: "Alternative text for screen readers",
28
+ validation: (Rule) => Rule.required(),
29
+ },
30
+ ],
31
+ }),
32
+ defineField({
33
+ name: "categories",
34
+ title: "Categories",
35
+ type: "array",
36
+ of: [{ type: "reference", to: [{ type: "blogCategory" }] }],
37
+ description: "Category tags for filtering",
38
+ }),
39
+ defineField({
40
+ name: "date",
41
+ title: "Publication Date",
42
+ type: "date",
43
+ options: { dateFormat: "YYYY-MM-DD" },
44
+ }),
45
+ defineField({
46
+ name: "author",
47
+ title: "Author",
48
+ type: "reference",
49
+ to: [{ type: "author" }],
50
+ }),
51
+ defineField({
52
+ name: "body",
53
+ title: "Body",
54
+ type: "richText",
55
+ description: "Article content",
56
+ }),
57
+ defineField({
58
+ name: "relatedPosts",
59
+ title: "Related Posts",
60
+ type: "array",
61
+ of: [
62
+ {
63
+ type: "reference",
64
+ to: [{ type: "page" }],
65
+ options: {
66
+ filter:
67
+ 'pageFolder->slug.current == "blog" && defined(slug.current) && slug.current != ""',
68
+ },
69
+ },
70
+ ],
71
+ description: "Manually curated related blog pages",
72
+ validation: (Rule) => Rule.max(3),
73
+ }),
74
+ ],
75
+ preview: {
76
+ select: {
77
+ title: "title",
78
+ date: "date",
79
+ media: "thumbnail",
80
+ },
81
+ prepare({ title, date, media }) {
82
+ return {
83
+ title: title || "Blog Content",
84
+ subtitle: date || "No date set",
85
+ media,
86
+ }
87
+ },
88
+ },
89
+ })
@@ -0,0 +1,29 @@
1
+ import { defineField, defineType } from "sanity"
2
+ import { descriptionIcon } from "@/lib/integrations/sanity/icons"
3
+
4
+ export const description = defineType({
5
+ name: "description",
6
+ title: "Description",
7
+ type: "document",
8
+ icon: descriptionIcon,
9
+ fields: [
10
+ defineField({
11
+ name: "description",
12
+ title: "Description",
13
+ type: "text",
14
+ rows: 4,
15
+ validation: (Rule) => Rule.required(),
16
+ }),
17
+ ],
18
+ preview: {
19
+ select: {
20
+ description: "description",
21
+ },
22
+ prepare({ description }) {
23
+ return {
24
+ title: "Description",
25
+ subtitle: description || "Untitled",
26
+ }
27
+ },
28
+ },
29
+ })
@@ -0,0 +1,28 @@
1
+ import { defineField, defineType } from "sanity"
2
+ import { heroIcon } from "@/lib/integrations/sanity/icons"
3
+
4
+ export const hero = defineType({
5
+ name: "hero",
6
+ title: "Heading",
7
+ type: "document",
8
+ icon: heroIcon,
9
+ fields: [
10
+ defineField({
11
+ name: "headline",
12
+ title: "Heading",
13
+ type: "string",
14
+ validation: (Rule) => Rule.required(),
15
+ }),
16
+ ],
17
+ preview: {
18
+ select: {
19
+ title: "headline",
20
+ },
21
+ prepare({ title }) {
22
+ return {
23
+ title: title || "Untitled",
24
+ subtitle: "Heading",
25
+ }
26
+ },
27
+ },
28
+ })
@@ -0,0 +1,45 @@
1
+ import { defineField, defineType } from "sanity"
2
+ import type { SanityStudioIcon } from "@/lib/integrations/sanity/icons"
3
+ import { pageListIcon } from "@/lib/integrations/sanity/icons"
4
+
5
+ function createContentCollection(config: {
6
+ name: string
7
+ title: string
8
+ icon: SanityStudioIcon
9
+ label: string
10
+ }) {
11
+ return defineType({
12
+ name: config.name,
13
+ title: config.title,
14
+ type: "document",
15
+ icon: config.icon,
16
+ description: `This component contains all the ${config.label}`,
17
+ fields: [
18
+ defineField({
19
+ name: "subtitle",
20
+ title: "Subtitle",
21
+ type: "string",
22
+ description:
23
+ "Text displayed between the featured articles and the rest of the collection",
24
+ }),
25
+ ],
26
+ preview: {
27
+ select: {
28
+ title: "title",
29
+ },
30
+ prepare({ title }) {
31
+ return {
32
+ title: title || config.title,
33
+ subtitle: `${config.title} with search and category filters`,
34
+ }
35
+ },
36
+ },
37
+ })
38
+ }
39
+
40
+ export const blogCollection = createContentCollection({
41
+ name: "blogCollection",
42
+ title: "Blog Collection",
43
+ icon: pageListIcon,
44
+ label: "blog articles",
45
+ })
@@ -0,0 +1,70 @@
1
+ import { defineField, defineType } from "sanity"
2
+ import { authorIcon } from "@/lib/integrations/sanity/icons"
3
+
4
+ export const author = defineType({
5
+ name: "author",
6
+ title: "Author",
7
+ type: "document",
8
+ icon: authorIcon,
9
+ fields: [
10
+ defineField({
11
+ name: "name",
12
+ title: "Name",
13
+ type: "string",
14
+ validation: (Rule) => Rule.required(),
15
+ }),
16
+ defineField({
17
+ name: "avatar",
18
+ title: "Avatar",
19
+ type: "image",
20
+ options: {
21
+ hotspot: true,
22
+ },
23
+ fields: [
24
+ {
25
+ name: "alt",
26
+ title: "Alt Text",
27
+ type: "string",
28
+ description: "Alternative text for screen readers",
29
+ },
30
+ ],
31
+ validation: (Rule) => Rule.required(),
32
+ }),
33
+ defineField({
34
+ name: "role",
35
+ title: "Role",
36
+ type: "string",
37
+ description: "Job title or role (e.g. CEO, Engineering Lead)",
38
+ }),
39
+ defineField({
40
+ name: "description",
41
+ title: "Description",
42
+ type: "text",
43
+ rows: 3,
44
+ description: "Short bio displayed on blog articles",
45
+ }),
46
+ defineField({
47
+ name: "linkedInUrl",
48
+ title: "LinkedIn URL",
49
+ type: "url",
50
+ validation: (Rule) =>
51
+ Rule.uri({
52
+ scheme: ["https"],
53
+ }),
54
+ }),
55
+ ],
56
+ preview: {
57
+ select: {
58
+ title: "name",
59
+ subtitle: "role",
60
+ media: "avatar",
61
+ },
62
+ },
63
+ orderings: [
64
+ {
65
+ title: "Name A-Z",
66
+ name: "nameAsc",
67
+ by: [{ field: "name", direction: "asc" }],
68
+ },
69
+ ],
70
+ })
@@ -0,0 +1,55 @@
1
+ import { defineField, defineType } from "sanity"
2
+ import { blogCategoryIcon } from "@/lib/integrations/sanity/icons"
3
+
4
+ export const blogCategory = defineType({
5
+ name: "blogCategory",
6
+ title: "Blog Category",
7
+ type: "document",
8
+ icon: blogCategoryIcon,
9
+ fields: [
10
+ defineField({
11
+ name: "title",
12
+ title: "Title",
13
+ type: "string",
14
+ description: "Category name (e.g. AI, Product Updates, Engineering)",
15
+ validation: (Rule) => Rule.required(),
16
+ }),
17
+ defineField({
18
+ name: "slug",
19
+ title: "Slug",
20
+ type: "slug",
21
+ description: "URL-friendly identifier used for filtering",
22
+ options: {
23
+ source: "title",
24
+ maxLength: 96,
25
+ },
26
+ validation: (Rule) =>
27
+ Rule.required().custom((slug) => {
28
+ if (!slug?.current) return "Required"
29
+ if (!/^[a-z0-9-]+$/.test(slug.current)) {
30
+ return "Slug must be lowercase with hyphens only"
31
+ }
32
+ return true
33
+ }),
34
+ }),
35
+ defineField({
36
+ name: "description",
37
+ title: "Description",
38
+ type: "text",
39
+ rows: 2,
40
+ description: "Brief description of this category",
41
+ }),
42
+ ],
43
+ preview: {
44
+ select: {
45
+ title: "title",
46
+ },
47
+ },
48
+ orderings: [
49
+ {
50
+ title: "Title A-Z",
51
+ name: "titleAsc",
52
+ by: [{ field: "title", direction: "asc" }],
53
+ },
54
+ ],
55
+ })
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Sanity Schema Types
3
+ *
4
+ * All schema definitions for Sanity CMS.
5
+ * Shared schemas live in `schemas/shared`, content documents live in
6
+ * `schemas/content`, component documents live in `schemas/components`, and
7
+ * layout documents live in `schemas/layout`.
8
+ */
9
+
10
+ import type { SchemaTypeDefinition } from "sanity"
11
+ import {
12
+ blogCollection,
13
+ blogContent,
14
+ description,
15
+ hero,
16
+ } from "./components"
17
+ import { author } from "./content/author"
18
+ import { blogCategory } from "./content/blog-category"
19
+ import { companyData } from "./layout/company-data"
20
+ import { footer } from "./layout/footer"
21
+ import { navbar } from "./layout/navbar"
22
+ import { externalLink, link, pageReference } from "./shared/link"
23
+ import { metadata } from "./shared/metadata"
24
+ import {
25
+ navColumn,
26
+ navItem,
27
+ navLeafItem,
28
+ navMegaMenu,
29
+ } from "./shared/nav-objects"
30
+ import { page } from "./shared/page"
31
+ import { pageBuilder } from "./shared/page-builder"
32
+ import { pageFolder } from "./shared/page-folder"
33
+ import { richText } from "./shared/richText"
34
+
35
+ // Re-export all schemas for convenience
36
+ export {
37
+ author,
38
+ blogCategory,
39
+ blogCollection,
40
+ blogContent,
41
+ companyData,
42
+ description,
43
+ externalLink,
44
+ footer,
45
+ hero,
46
+ link,
47
+ metadata,
48
+ navbar,
49
+ navColumn,
50
+ navItem,
51
+ navLeafItem,
52
+ navMegaMenu,
53
+ page,
54
+ pageBuilder,
55
+ pageFolder,
56
+ pageReference,
57
+ richText,
58
+ }
59
+
60
+ // Schema collection for Sanity configuration
61
+ export const schema: { types: SchemaTypeDefinition[] } = {
62
+ types: [
63
+ // Object types (reusable field types)
64
+ pageReference,
65
+ externalLink,
66
+ link,
67
+ metadata,
68
+ pageBuilder,
69
+ richText,
70
+
71
+ // Navigation object types (used by navbar)
72
+ navLeafItem,
73
+ navColumn,
74
+ navMegaMenu,
75
+ navItem,
76
+
77
+ // Component types (reusable document components)
78
+ blogCollection,
79
+ blogContent,
80
+ hero,
81
+ description,
82
+
83
+ // Layout types (singletons for site-wide layout)
84
+ companyData,
85
+ navbar,
86
+ footer,
87
+
88
+ // Document types (content pages)
89
+ page,
90
+ pageFolder,
91
+
92
+ // Content types (reusable content)
93
+ author,
94
+ blogCategory,
95
+ ],
96
+ }
@@ -0,0 +1,62 @@
1
+ import { defineArrayMember, defineField, defineType } from "sanity"
2
+ import { socialLinksIcon } from "@/lib/integrations/sanity/icons"
3
+
4
+ export const companyData = defineType({
5
+ name: "companyData",
6
+ title: "Company Data",
7
+ type: "document",
8
+ fields: [
9
+ defineField({
10
+ name: "socialLinks",
11
+ title: "Social Links",
12
+ type: "array",
13
+ of: [
14
+ defineArrayMember({
15
+ type: "object",
16
+ icon: socialLinksIcon,
17
+ fields: [
18
+ defineField({
19
+ name: "name",
20
+ title: "Name",
21
+ type: "string",
22
+ description: "Platform name (e.g. LinkedIn, TikTok, Bluesky)",
23
+ validation: (Rule) => Rule.required(),
24
+ }),
25
+ defineField({
26
+ name: "icon",
27
+ title: "Icon",
28
+ type: "image",
29
+ description: "Platform icon (SVG, PNG, WebP, etc.)",
30
+ validation: (Rule) => Rule.required(),
31
+ }),
32
+ defineField({
33
+ name: "url",
34
+ title: "URL",
35
+ type: "url",
36
+ validation: (Rule) => Rule.required(),
37
+ }),
38
+ defineField({
39
+ name: "label",
40
+ title: "Label",
41
+ type: "string",
42
+ description:
43
+ "Accessible label for the link (e.g. 'Follow us on LinkedIn')",
44
+ }),
45
+ ],
46
+ preview: {
47
+ select: {
48
+ title: "name",
49
+ subtitle: "url",
50
+ media: "icon",
51
+ },
52
+ },
53
+ }),
54
+ ],
55
+ }),
56
+ ],
57
+ preview: {
58
+ prepare() {
59
+ return { title: "Company Data" }
60
+ },
61
+ },
62
+ })