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,80 @@
1
+ import "@/lib/styles/global.css"
2
+
3
+ import type { Metadata } from "next"
4
+ import { Geist } from "next/font/google"
5
+ import type { PropsWithChildren } from "react"
6
+ import { cn } from "@/lib/styles/cn"
7
+ import { getBaseUrl } from "@/lib/utils/base-url"
8
+ import AppData from "@/package.json"
9
+
10
+ const APP_NAME = AppData.name
11
+ const APP_DEFAULT_TITLE = "My Site"
12
+ const APP_TITLE_TEMPLATE = "%s"
13
+ const APP_DESCRIPTION = AppData.description
14
+ const APP_BASE_URL = getBaseUrl()
15
+
16
+ const geist = Geist({
17
+ subsets: ["latin"],
18
+ variable: "--font-geist-variable",
19
+ })
20
+
21
+ export const metadata: Metadata = {
22
+ alternates: {
23
+ canonical: "/",
24
+ languages: {
25
+ "en-US": "/en-US",
26
+ },
27
+ },
28
+ appleWebApp: {
29
+ capable: true,
30
+ statusBarStyle: "default",
31
+ title: APP_DEFAULT_TITLE,
32
+ },
33
+ applicationName: APP_NAME,
34
+ authors: [{ name: "basement.studio", url: "https://basement.studio" }],
35
+ description: APP_DESCRIPTION,
36
+ formatDetection: { telephone: false },
37
+ metadataBase: new URL(APP_BASE_URL),
38
+ openGraph: {
39
+ description: APP_DESCRIPTION,
40
+ images: [
41
+ {
42
+ alt: APP_DEFAULT_TITLE,
43
+ height: 630,
44
+ url: "/opengraph-image.jpg",
45
+ width: 1200,
46
+ },
47
+ ],
48
+ locale: "en_US",
49
+ siteName: APP_NAME,
50
+ title: {
51
+ default: APP_DEFAULT_TITLE,
52
+ template: APP_TITLE_TEMPLATE,
53
+ },
54
+ type: "website",
55
+ url: APP_BASE_URL,
56
+ },
57
+ other: {
58
+ "fb:app_id": process.env.NEXT_PUBLIC_FACEBOOK_APP_ID || "",
59
+ },
60
+ title: {
61
+ default: APP_DEFAULT_TITLE,
62
+ template: APP_TITLE_TEMPLATE,
63
+ },
64
+ twitter: {
65
+ card: "summary_large_image",
66
+ description: APP_DESCRIPTION,
67
+ title: {
68
+ default: APP_DEFAULT_TITLE,
69
+ template: APP_TITLE_TEMPLATE,
70
+ },
71
+ },
72
+ }
73
+
74
+ const Layout = ({ children }: PropsWithChildren) => (
75
+ <html lang="en" dir="ltr" className={cn(geist.variable)}>
76
+ <body className="text-black">{children}</body>
77
+ </html>
78
+ )
79
+
80
+ export default Layout
@@ -0,0 +1,15 @@
1
+ import type { MetadataRoute } from "next";
2
+
3
+ const APP_BASE_URL =
4
+ process.env.NEXT_PUBLIC_BASE_URL ?? "http://localhost:3000";
5
+
6
+ export default function robots(): MetadataRoute.Robots {
7
+ return {
8
+ rules: {
9
+ userAgent: "*",
10
+ allow: "/",
11
+ disallow: [],
12
+ },
13
+ sitemap: `${APP_BASE_URL}/sitemap.xml`,
14
+ };
15
+ }
@@ -0,0 +1,124 @@
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
+ export const dynamic = "force-dynamic"
11
+
12
+ type PageDoc = PAGE_SITEMAP_QUERY_RESULT[number]
13
+
14
+ const ROOT_GROUP = "__root__"
15
+
16
+ const getFolderGroup = (slug: string | null) => {
17
+ if (!slug) return ROOT_GROUP
18
+
19
+ const segments = slug.split("/").filter(Boolean)
20
+
21
+ if (segments.length <= 1) return ROOT_GROUP
22
+
23
+ return segments.slice(0, -1).join("/")
24
+ }
25
+
26
+ const getGroupHeading = (group: string) => {
27
+ if (group === ROOT_GROUP) return "### Root Pages"
28
+
29
+ return `### /${group}`
30
+ }
31
+
32
+ const sortPages = (left: PageDoc, right: PageDoc) => {
33
+ const leftLabel = left.title || left.slug || "Homepage"
34
+ const rightLabel = right.title || right.slug || "Homepage"
35
+
36
+ return leftLabel.localeCompare(rightLabel)
37
+ }
38
+
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
+ return new NextResponse(parts.join("\n"))
117
+ } catch (error) {
118
+ console.error("Error generating sitemap.md:", error)
119
+ return new NextResponse("# Sitemap\n\nError fetching content.", {
120
+ status: 500,
121
+ headers: { "Content-Type": "text/markdown; charset=utf-8" },
122
+ })
123
+ }
124
+ }
@@ -0,0 +1,80 @@
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 { getPageUrl, getSitemap, getSitemapBaseUrl } from "@/lib/utils/sitemap"
5
+
6
+ export const dynamic = "force-dynamic"
7
+
8
+ type PageDoc = PAGE_SITEMAP_QUERY_RESULT[number]
9
+
10
+ interface SitemapEntry {
11
+ url: string
12
+ lastModified?: string | null
13
+ }
14
+
15
+ const createPageEntry = (
16
+ baseUrl: string,
17
+ slug: string | null,
18
+ updatedAt?: string | null
19
+ ): SitemapEntry => ({
20
+ url: getPageUrl(baseUrl, slug),
21
+ lastModified: updatedAt ?? null,
22
+ })
23
+
24
+ const escapeXml = (value: string) =>
25
+ value
26
+ .replaceAll("&", "&amp;")
27
+ .replaceAll("<", "&lt;")
28
+ .replaceAll(">", "&gt;")
29
+ .replaceAll('"', "&quot;")
30
+ .replaceAll("'", "&apos;")
31
+
32
+ const renderUrl = (entry: SitemapEntry) => {
33
+ const parts = [" <url>", ` <loc>${escapeXml(entry.url)}</loc>`]
34
+
35
+ if (entry.lastModified) {
36
+ parts.push(
37
+ ` <lastmod>${new Date(entry.lastModified).toISOString()}</lastmod>`
38
+ )
39
+ }
40
+
41
+ parts.push(" </url>")
42
+
43
+ return parts.join("\n")
44
+ }
45
+
46
+ const renderSitemap = (entries: SitemapEntry[]) =>
47
+ [
48
+ '<?xml version="1.0" encoding="UTF-8"?>',
49
+ '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
50
+ ...entries.map(renderUrl),
51
+ "</urlset>",
52
+ ].join("\n")
53
+
54
+ const getSitemapResponse = (entries: SitemapEntry[]) =>
55
+ new NextResponse(renderSitemap(entries))
56
+
57
+ export const GET = async () => {
58
+ const baseUrl = getSitemapBaseUrl()
59
+ const homepageEntry = createPageEntry(baseUrl, null)
60
+
61
+ if (!isSanityConfigured()) return getSitemapResponse([homepageEntry])
62
+
63
+ try {
64
+ const allPages = await getSitemap()
65
+
66
+ if (!allPages) return getSitemapResponse([homepageEntry])
67
+
68
+ const pages = (allPages ?? []).filter((page) => !page.noIndex)
69
+ const sitemapEntries = pages.map((page: PageDoc) =>
70
+ createPageEntry(baseUrl, page.slug ?? null, page._updatedAt)
71
+ )
72
+
73
+ return getSitemapResponse(
74
+ sitemapEntries.length > 0 ? sitemapEntries : [homepageEntry]
75
+ )
76
+ } catch (error) {
77
+ console.error("Error generating sitemap.xml:", error)
78
+ return getSitemapResponse([homepageEntry])
79
+ }
80
+ }
@@ -0,0 +1,8 @@
1
+ "use client"
2
+
3
+ import { NextStudio } from "next-sanity/studio"
4
+ import config from "@/lib/integrations/sanity/sanity.config"
5
+
6
+ const StudioPage = () => <NextStudio config={config} />
7
+
8
+ export default StudioPage
@@ -0,0 +1,239 @@
1
+ {
2
+ "root": true,
3
+ "$schema": "node_modules/@biomejs/biome/configuration_schema.json",
4
+ "assist": {
5
+ "actions": {
6
+ "source": {
7
+ "organizeImports": "on"
8
+ }
9
+ }
10
+ },
11
+ "css": {
12
+ "formatter": {
13
+ "enabled": true
14
+ },
15
+ "linter": {
16
+ "enabled": true
17
+ },
18
+ "parser": {
19
+ "cssModules": true,
20
+ "tailwindDirectives": true
21
+ }
22
+ },
23
+ "files": {
24
+ "ignoreUnknown": true,
25
+ "includes": [
26
+ "**",
27
+ "!node_modules",
28
+ "!**/.next",
29
+ "!**/dist",
30
+ "!**/public",
31
+ "!.github",
32
+ "!.vercel",
33
+ "!pnpm-lock.yaml",
34
+ "!bun.lock",
35
+ "!**/*.md",
36
+ "!**/*.mdx",
37
+ "!**/tailwind.css",
38
+ "!**/*.grit"
39
+ ]
40
+ },
41
+ "formatter": {
42
+ "enabled": true,
43
+ "indentStyle": "space",
44
+ "indentWidth": 2,
45
+ "lineEnding": "lf",
46
+ "lineWidth": 80
47
+ },
48
+ "javascript": {
49
+ "formatter": {
50
+ "enabled": true,
51
+ "quoteStyle": "double",
52
+ "semicolons": "asNeeded",
53
+ "trailingCommas": "es5"
54
+ }
55
+ },
56
+ "json": {
57
+ "parser": {
58
+ "allowComments": true
59
+ }
60
+ },
61
+ "linter": {
62
+ "domains": {
63
+ "next": "recommended",
64
+ "react": "recommended"
65
+ },
66
+ "enabled": true,
67
+ "rules": {
68
+ "a11y": {
69
+ "noAriaUnsupportedElements": "error",
70
+ "noAutofocus": "warn",
71
+ "noDistractingElements": "error",
72
+ "noRedundantAlt": "error",
73
+ "useAltText": "error",
74
+ "useButtonType": "error",
75
+ "useKeyWithClickEvents": "warn",
76
+ "useSemanticElements": "warn",
77
+ "useValidAnchor": "warn",
78
+ "useValidAriaProps": "error",
79
+ "useValidAriaRole": "error",
80
+ "useValidAriaValues": "error"
81
+ },
82
+ "complexity": {
83
+ "noForEach": "off",
84
+ "useFlatMap": "warn",
85
+ "useSimplifiedLogicExpression": "warn"
86
+ },
87
+ "correctness": {
88
+ "noInvalidUseBeforeDeclaration": "error",
89
+ "noUnknownMediaFeatureName": "off",
90
+ "noUnusedFunctionParameters": "warn",
91
+ "noUnusedImports": "error",
92
+ "noUnusedVariables": "error",
93
+ "useExhaustiveDependencies": "warn"
94
+ },
95
+ "nursery": {
96
+ "useSortedClasses": {
97
+ "level": "warn",
98
+ "options": {
99
+ "attributes": ["class", "className"],
100
+ "functions": ["cn", "cva"]
101
+ }
102
+ }
103
+ },
104
+ "performance": {
105
+ "noImgElement": "error"
106
+ },
107
+ "recommended": true,
108
+ "security": {
109
+ "noDangerouslySetInnerHtml": "warn",
110
+ "noDangerouslySetInnerHtmlWithChildren": "error",
111
+ "noGlobalEval": "error"
112
+ },
113
+ "style": {
114
+ "noInferrableTypes": "error",
115
+ "noNestedTernary": "error",
116
+ "noNonNullAssertion": "off",
117
+ "noParameterAssign": "error",
118
+ "noUnusedTemplateLiteral": "off",
119
+ "noUselessElse": "error",
120
+ "useAsConstAssertion": "error",
121
+ "useCollapsedElseIf": "warn",
122
+ "useConsistentArrayType": "error",
123
+ "useConsistentBuiltinInstantiation": "error",
124
+ "useDefaultParameterLast": "error",
125
+ "useEnumInitializers": "error",
126
+ "useExponentiationOperator": "error",
127
+ "useFilenamingConvention": {
128
+ "level": "warn",
129
+ "options": {
130
+ "filenameCases": ["kebab-case", "camelCase"],
131
+ "strictCase": false
132
+ }
133
+ },
134
+ "useForOf": "warn",
135
+ "useNumberNamespace": "error",
136
+ "useSelfClosingElements": "error",
137
+ "useShorthandAssign": "error",
138
+ "useSingleVarDeclarator": "error",
139
+ "useTemplate": "warn"
140
+ },
141
+ "suspicious": {
142
+ "noDebugger": "warn",
143
+ "noDoubleEquals": "error",
144
+ "noEmptyBlockStatements": "warn",
145
+ "noExplicitAny": "error",
146
+ "noGlobalIsFinite": "error",
147
+ "noGlobalIsNan": "error",
148
+ "noMisleadingCharacterClass": "error",
149
+ "noPrototypeBuiltins": "warn",
150
+ "noSelfCompare": "error",
151
+ "noSparseArray": "error",
152
+ "useAwait": "off"
153
+ }
154
+ }
155
+ },
156
+ "overrides": [
157
+ {
158
+ "includes": ["**/*.css"],
159
+ "linter": {
160
+ "rules": {
161
+ "correctness": {
162
+ "noUnknownFunction": "off"
163
+ }
164
+ }
165
+ }
166
+ },
167
+ {
168
+ "includes": ["**/*.tsx", "**/*.jsx"],
169
+ "linter": {
170
+ "rules": {
171
+ "a11y": {
172
+ "useKeyWithClickEvents": "error",
173
+ "useKeyWithMouseEvents": "error",
174
+ "useValidAnchor": "error"
175
+ },
176
+ "correctness": {
177
+ "useJsxKeyInIterable": "error"
178
+ }
179
+ }
180
+ }
181
+ },
182
+ {
183
+ "includes": ["**/*.ts", "**/*.tsx"],
184
+ "linter": {
185
+ "rules": {
186
+ "correctness": {
187
+ "noUndeclaredVariables": "off"
188
+ },
189
+ "style": {
190
+ "useConsistentArrayType": "error",
191
+ "useExportType": "error",
192
+ "useImportType": "error"
193
+ }
194
+ }
195
+ }
196
+ },
197
+ {
198
+ "includes": [
199
+ "app/**/*.tsx",
200
+ "app/**/*.ts",
201
+ "app/**/*.jsx",
202
+ "app/**/*.js"
203
+ ],
204
+ "linter": {
205
+ "rules": {
206
+ "style": {
207
+ "noDefaultExport": "off"
208
+ },
209
+ "suspicious": {
210
+ "useAwait": "off"
211
+ }
212
+ }
213
+ }
214
+ },
215
+ {
216
+ "includes": ["**/*.module.css"],
217
+ "linter": {
218
+ "rules": {
219
+ "correctness": {
220
+ "noUnknownProperty": "off"
221
+ },
222
+ "style": {
223
+ "noDescendingSpecificity": "off"
224
+ }
225
+ }
226
+ }
227
+ }
228
+ ],
229
+ "plugins": [
230
+ "./.biome/plugins/no-anchor-element.grit",
231
+ "./.biome/plugins/no-unnecessary-forwardref.grit",
232
+ "./.biome/plugins/no-relative-parent-imports.grit"
233
+ ],
234
+ "vcs": {
235
+ "clientKind": "git",
236
+ "enabled": true,
237
+ "useIgnoreFile": false
238
+ }
239
+ }
@@ -0,0 +1,95 @@
1
+ import { Link } from "@/components/ui/link"
2
+ import { SanityImage } from "@/components/ui/sanity-image"
3
+ import {
4
+ type CompanyData,
5
+ type FooterData,
6
+ getCompanyData,
7
+ getFooter,
8
+ } from "@/lib/integrations/sanity/fetchers/layout"
9
+
10
+ type FooterLinkGroup = NonNullable<FooterData["links"]>[number]
11
+ type FooterLinkItem = NonNullable<FooterLinkGroup["items"]>[number]
12
+ type SocialLink = NonNullable<CompanyData["socialLinks"]>[number]
13
+
14
+ export const Footer = async () => {
15
+ const [footer, companyData] = await Promise.all([
16
+ getFooter(),
17
+ getCompanyData(),
18
+ ])
19
+
20
+ const year = new Date().getFullYear()
21
+
22
+ if (!(footer || companyData)) {
23
+ return (
24
+ <footer className="border-black/10 border-t px-6 py-3">
25
+ <p>&copy; {year} Basement</p>
26
+ </footer>
27
+ )
28
+ }
29
+
30
+ const links = footer?.links ?? []
31
+
32
+ return (
33
+ <footer className="border-black/10 border-t">
34
+ <div className="mx-auto flex max-w-7xl flex-col gap-8 px-6 py-6">
35
+ {links.length > 0 ? (
36
+ <div className="grid grid-cols-5 gap-x-8 gap-y-6 md:grid-cols-3 lg:grid-cols-5">
37
+ {links.map((linkGroup: FooterLinkGroup) => (
38
+ <div key={linkGroup._key} className="flex flex-col gap-2">
39
+ <h3>{linkGroup.title}</h3>
40
+ {linkGroup.items && linkGroup.items.length > 0 ? (
41
+ <ul className="space-y-1">
42
+ {linkGroup.items
43
+ .filter(
44
+ (item: FooterLinkItem) => item.href && item.href !== "#"
45
+ )
46
+ .map((item: FooterLinkItem) => (
47
+ <li key={item._key}>
48
+ <Link href={item.href!}>{item.label}</Link>
49
+ </li>
50
+ ))}
51
+ </ul>
52
+ ) : null}
53
+ </div>
54
+ ))}
55
+ </div>
56
+ ) : (
57
+ <Link href="/">Basement</Link>
58
+ )}
59
+
60
+ <div className="flex flex-row items-center justify-between gap-3 border-black/10 border-t pt-4">
61
+ <p className="w-fit">&copy; {year} Basement</p>
62
+
63
+ {companyData?.socialLinks && companyData.socialLinks.length > 0 ? (
64
+ <div className="flex w-fit flex-wrap items-center gap-x-4 gap-y-2">
65
+ {companyData.socialLinks
66
+ .filter((social: SocialLink) => social.url)
67
+ .map((social: SocialLink) => (
68
+ <Link
69
+ key={social._key}
70
+ href={social.url!}
71
+ aria-label={
72
+ social.label ||
73
+ `Follow us on ${social.name || "social media"}`
74
+ }
75
+ >
76
+ {social.icon ? (
77
+ <SanityImage
78
+ image={social.icon}
79
+ alt={social.name || ""}
80
+ width={20}
81
+ height={20}
82
+ maxWidth={40}
83
+ />
84
+ ) : (
85
+ <span>{social.name}</span>
86
+ )}
87
+ </Link>
88
+ ))}
89
+ </div>
90
+ ) : null}
91
+ </div>
92
+ </div>
93
+ </footer>
94
+ )
95
+ }
@@ -0,0 +1,28 @@
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 type { CtaButton } from "./types"
6
+
7
+ export function CtaButtonRenderer({ cta }: { cta: CtaButton }) {
8
+ const linkAttributes = cta.link ? getLinkAttributes(cta.link) : undefined
9
+ const href = linkAttributes?.href
10
+
11
+ if (!(cta.label && href && href !== "#")) return null
12
+
13
+ return (
14
+ <Link
15
+ href={href}
16
+ target={linkAttributes?.target}
17
+ rel={linkAttributes?.rel}
18
+ className={cn(
19
+ "rounded-lg px-4 py-2 font-medium text-sm transition-opacity hover:opacity-80",
20
+ cta.variant === "primary"
21
+ ? "bg-black text-white"
22
+ : "border border-black/20 text-black"
23
+ )}
24
+ >
25
+ {cta.label}
26
+ </Link>
27
+ )
28
+ }