create-agntcms-app 0.2.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.
- package/README.md +39 -0
- package/dist/index.mjs +297 -0
- package/dist/template/.claude/settings.json +6 -0
- package/dist/template/.claude/skills/.gitkeep +0 -0
- package/dist/template/.claude-plugin/channel/server.mjs +254 -0
- package/dist/template/.claude-plugin/channel/server.ts +369 -0
- package/dist/template/.claude-plugin/plugin.json +17 -0
- package/dist/template/.mcp.json +8 -0
- package/dist/template/BRAND.md +49 -0
- package/dist/template/CLAUDE.md +157 -0
- package/dist/template/agntcms/config.ts +49 -0
- package/dist/template/agntcms/sections/ArticleBody/component.tsx +32 -0
- package/dist/template/agntcms/sections/ArticleBody/index.ts +10 -0
- package/dist/template/agntcms/sections/ArticleBody/schema.ts +5 -0
- package/dist/template/agntcms/sections/ArticleHero/component.tsx +87 -0
- package/dist/template/agntcms/sections/ArticleHero/index.ts +10 -0
- package/dist/template/agntcms/sections/ArticleHero/schema.ts +12 -0
- package/dist/template/agntcms/sections/Banner/component.tsx +83 -0
- package/dist/template/agntcms/sections/Banner/index.ts +10 -0
- package/dist/template/agntcms/sections/Banner/schema.ts +9 -0
- package/dist/template/agntcms/sections/BlogIndex/component.tsx +173 -0
- package/dist/template/agntcms/sections/BlogIndex/index.ts +10 -0
- package/dist/template/agntcms/sections/BlogIndex/schema.ts +33 -0
- package/dist/template/agntcms/sections/BlogIndexHeader/component.tsx +44 -0
- package/dist/template/agntcms/sections/BlogIndexHeader/index.ts +10 -0
- package/dist/template/agntcms/sections/BlogIndexHeader/schema.ts +8 -0
- package/dist/template/agntcms/sections/BlogPostBody/component.tsx +50 -0
- package/dist/template/agntcms/sections/BlogPostBody/index.ts +10 -0
- package/dist/template/agntcms/sections/BlogPostBody/schema.ts +10 -0
- package/dist/template/agntcms/sections/BlogPostHero/component.tsx +88 -0
- package/dist/template/agntcms/sections/BlogPostHero/index.ts +10 -0
- package/dist/template/agntcms/sections/BlogPostHero/schema.ts +35 -0
- package/dist/template/agntcms/sections/CaseStudies/component.tsx +92 -0
- package/dist/template/agntcms/sections/CaseStudies/index.ts +10 -0
- package/dist/template/agntcms/sections/CaseStudies/schema.ts +17 -0
- package/dist/template/agntcms/sections/ContactForm/component.tsx +119 -0
- package/dist/template/agntcms/sections/ContactForm/index.ts +10 -0
- package/dist/template/agntcms/sections/ContactForm/schema.ts +15 -0
- package/dist/template/agntcms/sections/DocsArticle/component.tsx +266 -0
- package/dist/template/agntcms/sections/DocsArticle/index.ts +10 -0
- package/dist/template/agntcms/sections/DocsArticle/schema.ts +33 -0
- package/dist/template/agntcms/sections/FAQ/component.tsx +57 -0
- package/dist/template/agntcms/sections/FAQ/index.ts +10 -0
- package/dist/template/agntcms/sections/FAQ/schema.ts +11 -0
- package/dist/template/agntcms/sections/FeatureGrid/component.tsx +117 -0
- package/dist/template/agntcms/sections/FeatureGrid/index.ts +10 -0
- package/dist/template/agntcms/sections/FeatureGrid/schema.ts +21 -0
- package/dist/template/agntcms/sections/FeaturedArticles/component.tsx +99 -0
- package/dist/template/agntcms/sections/FeaturedArticles/index.ts +10 -0
- package/dist/template/agntcms/sections/FeaturedArticles/schema.ts +17 -0
- package/dist/template/agntcms/sections/GettingStarted/component.tsx +116 -0
- package/dist/template/agntcms/sections/GettingStarted/index.ts +10 -0
- package/dist/template/agntcms/sections/GettingStarted/schema.ts +11 -0
- package/dist/template/agntcms/sections/Hero/component.tsx +148 -0
- package/dist/template/agntcms/sections/Hero/index.ts +10 -0
- package/dist/template/agntcms/sections/Hero/schema.ts +16 -0
- package/dist/template/agntcms/sections/HowItWorks/component.tsx +57 -0
- package/dist/template/agntcms/sections/HowItWorks/index.ts +10 -0
- package/dist/template/agntcms/sections/HowItWorks/schema.ts +11 -0
- package/dist/template/agntcms/sections/ImageText/component.tsx +110 -0
- package/dist/template/agntcms/sections/ImageText/index.ts +10 -0
- package/dist/template/agntcms/sections/ImageText/schema.ts +14 -0
- package/dist/template/agntcms/sections/LogoStrip/component.tsx +37 -0
- package/dist/template/agntcms/sections/LogoStrip/index.ts +10 -0
- package/dist/template/agntcms/sections/LogoStrip/schema.ts +6 -0
- package/dist/template/agntcms/sections/Newsletter/component.tsx +48 -0
- package/dist/template/agntcms/sections/Newsletter/index.ts +10 -0
- package/dist/template/agntcms/sections/Newsletter/schema.ts +8 -0
- package/dist/template/agntcms/sections/OpenSource/component.tsx +99 -0
- package/dist/template/agntcms/sections/OpenSource/index.ts +10 -0
- package/dist/template/agntcms/sections/OpenSource/schema.ts +13 -0
- package/dist/template/agntcms/sections/PainAnswer/component.tsx +81 -0
- package/dist/template/agntcms/sections/PainAnswer/index.ts +10 -0
- package/dist/template/agntcms/sections/PainAnswer/schema.ts +15 -0
- package/dist/template/agntcms/sections/PricingPlans/component.tsx +100 -0
- package/dist/template/agntcms/sections/PricingPlans/index.ts +10 -0
- package/dist/template/agntcms/sections/PricingPlans/schema.ts +13 -0
- package/dist/template/agntcms/sections/Problem/component.tsx +49 -0
- package/dist/template/agntcms/sections/Problem/index.ts +10 -0
- package/dist/template/agntcms/sections/Problem/schema.ts +12 -0
- package/dist/template/agntcms/sections/SiteFooter/component.tsx +88 -0
- package/dist/template/agntcms/sections/SiteFooter/index.ts +10 -0
- package/dist/template/agntcms/sections/SiteFooter/schema.ts +13 -0
- package/dist/template/agntcms/sections/SiteHeader/component.tsx +99 -0
- package/dist/template/agntcms/sections/SiteHeader/index.ts +10 -0
- package/dist/template/agntcms/sections/SiteHeader/schema.ts +14 -0
- package/dist/template/agntcms/sections/SiteMeta/component.tsx +26 -0
- package/dist/template/agntcms/sections/SiteMeta/index.ts +13 -0
- package/dist/template/agntcms/sections/SiteMeta/schema.ts +18 -0
- package/dist/template/agntcms/sections/TabbedFeatures/component.tsx +120 -0
- package/dist/template/agntcms/sections/TabbedFeatures/index.ts +10 -0
- package/dist/template/agntcms/sections/TabbedFeatures/schema.ts +13 -0
- package/dist/template/agntcms/sections/TeamGrid/component.tsx +77 -0
- package/dist/template/agntcms/sections/TeamGrid/index.ts +10 -0
- package/dist/template/agntcms/sections/TeamGrid/schema.ts +14 -0
- package/dist/template/agntcms/sections/Testimonials/component.tsx +76 -0
- package/dist/template/agntcms/sections/Testimonials/index.ts +10 -0
- package/dist/template/agntcms/sections/Testimonials/schema.ts +12 -0
- package/dist/template/agntcms/sections/WhatIsBuilt/component.tsx +86 -0
- package/dist/template/agntcms/sections/WhatIsBuilt/index.ts +10 -0
- package/dist/template/agntcms/sections/WhatIsBuilt/schema.ts +20 -0
- package/dist/template/agntcms/site-meta.ts +81 -0
- package/dist/template/app/[[...slug]]/page.tsx +123 -0
- package/dist/template/app/admin/AdminPageClient.tsx +77 -0
- package/dist/template/app/admin/AdminPageDynamic.tsx +24 -0
- package/dist/template/app/admin/page.tsx +14 -0
- package/dist/template/app/api/agntcms/_shared.ts +80 -0
- package/dist/template/app/api/agntcms/assets/route.ts +11 -0
- package/dist/template/app/api/agntcms/assets/upload/route.ts +11 -0
- package/dist/template/app/api/agntcms/draft/discard/route.ts +12 -0
- package/dist/template/app/api/agntcms/draft/list/route.ts +11 -0
- package/dist/template/app/api/agntcms/draft/publish/route.ts +11 -0
- package/dist/template/app/api/agntcms/draft/reorder/route.ts +10 -0
- package/dist/template/app/api/agntcms/draft/save/route.ts +11 -0
- package/dist/template/app/api/agntcms/events/route.ts +12 -0
- package/dist/template/app/api/agntcms/forms/delete/route.ts +17 -0
- package/dist/template/app/api/agntcms/forms/list/route.ts +24 -0
- package/dist/template/app/api/agntcms/forms/read/route.ts +23 -0
- package/dist/template/app/api/agntcms/forms/submit/route.ts +17 -0
- package/dist/template/app/api/agntcms/global/delete/route.ts +13 -0
- package/dist/template/app/api/agntcms/global/history/route.ts +10 -0
- package/dist/template/app/api/agntcms/global/list/route.ts +14 -0
- package/dist/template/app/api/agntcms/global/read/route.ts +11 -0
- package/dist/template/app/api/agntcms/global/rollback/route.ts +10 -0
- package/dist/template/app/api/agntcms/global/save/route.ts +14 -0
- package/dist/template/app/api/agntcms/mcp/route.ts +12 -0
- package/dist/template/app/api/agntcms/page/delete/route.ts +10 -0
- package/dist/template/app/api/agntcms/page/duplicate/route.ts +11 -0
- package/dist/template/app/api/agntcms/page/history/route.ts +10 -0
- package/dist/template/app/api/agntcms/page/list/route.ts +10 -0
- package/dist/template/app/api/agntcms/page/read/route.ts +11 -0
- package/dist/template/app/api/agntcms/page/rename/route.ts +10 -0
- package/dist/template/app/api/agntcms/page/rollback/route.ts +10 -0
- package/dist/template/app/api/agntcms/page/unpublish/route.ts +11 -0
- package/dist/template/app/api/agntcms/preview/enter/route.ts +13 -0
- package/dist/template/app/api/agntcms/preview/exit/route.ts +10 -0
- package/dist/template/app/api/agntcms/preview/issue/route.ts +12 -0
- package/dist/template/app/api/agntcms/template/list/route.ts +15 -0
- package/dist/template/app/apple-icon.svg +9 -0
- package/dist/template/app/icon.svg +9 -0
- package/dist/template/app/layout.tsx +107 -0
- package/dist/template/app/not-found.tsx +75 -0
- package/dist/template/app/robots.ts +33 -0
- package/dist/template/app/sitemap.ts +49 -0
- package/dist/template/content/globals/site-footer.json +53 -0
- package/dist/template/content/globals/site-header.json +18 -0
- package/dist/template/content/globals/site-meta.json +13 -0
- package/dist/template/content/pages/404.json +34 -0
- package/dist/template/content/pages/about.json +307 -0
- package/dist/template/content/pages/article-editor.json +61 -0
- package/dist/template/content/pages/article-schemas.json +61 -0
- package/dist/template/content/pages/blog.json +162 -0
- package/dist/template/content/pages/contact.json +29 -0
- package/dist/template/content/pages/home.json +243 -0
- package/dist/template/content/pages/pricing.json +219 -0
- package/dist/template/content/pages/services.json +177 -0
- package/dist/template/fonts/Satoshi-Medium.woff2 +0 -0
- package/dist/template/fonts/Satoshi-Regular.woff2 +0 -0
- package/dist/template/next.config.ts +6 -0
- package/dist/template/package.json +36 -0
- package/dist/template/postcss.config.mjs +5 -0
- package/dist/template/public/assets/.gitkeep +0 -0
- package/dist/template/public/assets/0418d7ed21f57e7b9e0546725c92b8419daeaa355675d9070fab0c2013cf1524.jpg +0 -0
- package/dist/template/public/assets/0d0475f21aa96435a8ed3cdb2fddcc6278492e76ae842f569432454f4d33631a.jpg +0 -0
- package/dist/template/public/assets/27457a1adee2372030d9876b0d52c44d46be98843999935eaef2526b9b961f12.jpg +0 -0
- package/dist/template/public/assets/3855d91192f0c6120b01427b78ef84e52baa9f4b5a17d4271e41c1bfd95a5b0c.jpg +0 -0
- package/dist/template/public/assets/3b3b90c5084635b746be673ede92a328f002f5621a42c9a5cb89c5e2435652cb.jpg +0 -0
- package/dist/template/public/assets/3e76165a78fd3e7b8ed1e93dee50803ae11110c756c8c1c89229a2dec2bc0abf.jpg +0 -0
- package/dist/template/public/assets/4a3e28f85dc850c347ea0fd931696aa936a6bd45f193e7f1c9328b5896fb272c.jpg +0 -0
- package/dist/template/public/assets/579f67d5fd4c9106c6cdf2ef29f50df934ad0fc2b7849bac1e1cfb1e3f92303b.jpg +0 -0
- package/dist/template/public/assets/5b95209269661bb60fb250f1da682e05b9efa64dd42f350608b299e6bf1f2f35.jpg +0 -0
- package/dist/template/public/assets/5e04b46f8317ef95a7ddf85aedfe5c098a755f05056325d0251eccf95ce51172.jpg +0 -0
- package/dist/template/public/assets/6167a9164be2cf1183bdfdd4946bf9b908570e79e92a2380c25f0bb702422bbd.jpg +0 -0
- package/dist/template/public/assets/75e723ec316de28247924e5dfb73a4b266e10de605e749f150883d280ed8ed16.jpg +0 -0
- package/dist/template/public/assets/816a11e6a7245feaf51bbebf09d1bda3f125b334bc24fc3b8f47b5380a7b4294.jpg +0 -0
- package/dist/template/public/assets/81eba6f5654b8746a9b0cba1a9521a67f2b4afaaefc7c88d66dfab1461270d8f.jpg +0 -0
- package/dist/template/public/assets/82a2ce9e49361098f77a28755779dc5a7c026831cbd135175749c1304e21dacc.jpg +0 -0
- package/dist/template/public/assets/8d7b02ba277ba56bdafdbd47b01f7df6d993c714b4dc2305eb65a1307c09647d.jpg +0 -0
- package/dist/template/public/assets/b303185b471678e4d62f678a1549ee26022f4745407d08cae44ecb1c25352293.jpg +0 -0
- package/dist/template/public/assets/b69b49169c11546100d6dd5280073bc0d84cbbcc6d33fa01ecf6a5866fa42237.jpg +0 -0
- package/dist/template/public/assets/c4d2f0d1a310e457ac722a399693652e3c86c55b294243d5ffc679394e12f9d1.jpg +0 -0
- package/dist/template/public/assets/cae09f4729f8a348b67267c2f2a550be0f3bfa420689afe1a5cf8b7e2b146238.png +0 -0
- package/dist/template/public/assets/cb3acf58b57417a4b26474ba04c096af7103c4320ed2f4f3683f79d7670a055c.jpg +0 -0
- package/dist/template/public/assets/d5a0701b2d156284e0ce851cd2534ec632db34f91fbcbee3b8a7784d45ce78d2.jpg +0 -0
- package/dist/template/public/assets/d6ef1c3f48b0e488521794fb60701da1fd2c3a1621d6ac5f17ccfd4909d3be60.jpg +0 -0
- package/dist/template/public/assets/de249ff9be2539cf0d1ce092de3c57001839b6c3e14fcee3fc31a7b7673ae007.jpg +0 -0
- package/dist/template/public/assets/eac45438956be187b010e24b3289757aa00f227c190d49ee99fea510552dd2ba.jpg +0 -0
- package/dist/template/public/assets/f8b9200065b5436c6a88361839edc2b89be88d3037c84a80d3ee95c32891510b.jpg +0 -0
- package/dist/template/public/assets/placeholder.png +0 -0
- package/dist/template/public/brand/mark.svg +6 -0
- package/dist/template/public/brand/wordmark-light.svg +6 -0
- package/dist/template/public/brand/wordmark.svg +6 -0
- package/dist/template/styles/globals.css +69 -0
- package/dist/template/styles/theme.css +492 -0
- package/dist/template/styles/typography.css +469 -0
- package/dist/template/tsconfig.json +30 -0
- package/package.json +30 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { EditableRichText, EditableList } from '@agntcms/next/client'
|
|
4
|
+
import type { EditableSlot, SlotItem } from '@agntcms/next/client'
|
|
5
|
+
import { schema } from './schema'
|
|
6
|
+
|
|
7
|
+
type Row = SlotItem<typeof schema.rows.itemSchema>
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
readonly eyebrow: EditableSlot<'richText', string>
|
|
11
|
+
readonly headline: EditableSlot<'richText', string>
|
|
12
|
+
readonly intro: EditableSlot<'richText', string>
|
|
13
|
+
readonly painLabel: EditableSlot<'richText', string>
|
|
14
|
+
readonly oursLabel: EditableSlot<'richText', string>
|
|
15
|
+
readonly rows: EditableSlot<'list', ReadonlyArray<Row>>
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function PainAnswerComponent({ eyebrow, headline, intro, painLabel, oursLabel, rows }: Props) {
|
|
19
|
+
return (
|
|
20
|
+
<section id="vs" className="bg-bg-primary">
|
|
21
|
+
<div className="mx-auto max-w-[1080px] px-8 py-16 border-t-[0.5px] border-border-secondary">
|
|
22
|
+
<EditableRichText
|
|
23
|
+
field={eyebrow}
|
|
24
|
+
className="prose mb-3.5 [&_p]:text-[11px] [&_p]:font-medium [&_p]:tracking-[0.10em] [&_p]:uppercase [&_p]:text-text-brand-primary [&_p]:m-0"
|
|
25
|
+
/>
|
|
26
|
+
<EditableRichText
|
|
27
|
+
field={headline}
|
|
28
|
+
className="mb-6
|
|
29
|
+
[&_h2]:font-display [&_h2]:font-medium [&_h2]:text-text-primary [&_h2]:m-0
|
|
30
|
+
[&_h2]:max-w-[22ch]
|
|
31
|
+
[&_h2]:!text-[clamp(28px,3.6vw,40px)] [&_h2]:!leading-[1.1] [&_h2]:!tracking-[-0.02em]"
|
|
32
|
+
/>
|
|
33
|
+
<EditableRichText
|
|
34
|
+
field={intro}
|
|
35
|
+
className="prose max-w-[60ch] mb-9 [&_p]:text-text-primary [&_p]:m-0"
|
|
36
|
+
/>
|
|
37
|
+
|
|
38
|
+
<div className="border-t-[0.5px] border-border-primary mt-2">
|
|
39
|
+
{/* Header row */}
|
|
40
|
+
<div className="hidden sm:grid grid-cols-2 border-b-[0.5px] border-border-primary">
|
|
41
|
+
<EditableRichText
|
|
42
|
+
field={painLabel}
|
|
43
|
+
className="prose px-6 py-3.5 [&_p]:text-[11px] [&_p]:font-medium [&_p]:tracking-[0.10em] [&_p]:uppercase [&_p]:text-text-secondary [&_p]:m-0"
|
|
44
|
+
/>
|
|
45
|
+
<EditableRichText
|
|
46
|
+
field={oursLabel}
|
|
47
|
+
className="prose px-6 py-3.5 border-l-[0.5px] border-border-primary [&_p]:text-[11px] [&_p]:font-medium [&_p]:tracking-[0.10em] [&_p]:uppercase [&_p]:text-text-brand-primary [&_p]:m-0"
|
|
48
|
+
/>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<EditableList
|
|
52
|
+
field={rows}
|
|
53
|
+
itemSchema={schema.rows.itemSchema}
|
|
54
|
+
className="contents"
|
|
55
|
+
renderItem={(row, index) => {
|
|
56
|
+
const num = String(index + 1).padStart(2, '0')
|
|
57
|
+
return (
|
|
58
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 border-b-[0.5px] border-border-primary">
|
|
59
|
+
<div className="flex gap-[18px] px-6 py-6 items-start text-text-tertiary">
|
|
60
|
+
<div className="font-mono text-[11px] tracking-[0.06em] text-text-tertiary mt-[3px] shrink-0 w-[18px]">{num}</div>
|
|
61
|
+
<EditableRichText
|
|
62
|
+
field={row.pain}
|
|
63
|
+
className="prose [&_p]:m-0 [&_p]:text-[15px] [&_p]:leading-[1.55] [&_p]:line-through [&_p]:decoration-text-tertiary [&_p]:decoration-[0.5px]"
|
|
64
|
+
/>
|
|
65
|
+
</div>
|
|
66
|
+
<div className="flex gap-[18px] px-6 py-6 items-start bg-bg-secondary border-t-[0.5px] sm:border-t-0 sm:border-l-[0.5px] border-border-primary">
|
|
67
|
+
<div className="font-mono text-[11px] tracking-[0.06em] text-text-brand-primary mt-[3px] shrink-0 w-[18px]">→</div>
|
|
68
|
+
<EditableRichText
|
|
69
|
+
field={row.ours}
|
|
70
|
+
className="prose [&_p]:m-0 [&_p]:text-[15px] [&_p]:leading-[1.55] [&_p]:text-text-primary"
|
|
71
|
+
/>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
)
|
|
75
|
+
}}
|
|
76
|
+
/>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
</section>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { defineSection } from '@agntcms/next'
|
|
2
|
+
import { schema } from './schema'
|
|
3
|
+
import { PainAnswerComponent } from './component'
|
|
4
|
+
|
|
5
|
+
export const PainAnswer = defineSection({
|
|
6
|
+
name: 'PainAnswer',
|
|
7
|
+
category: 'Content',
|
|
8
|
+
schema,
|
|
9
|
+
component: PainAnswerComponent,
|
|
10
|
+
})
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { RichTextField, ListField } from '@agntcms/next'
|
|
2
|
+
|
|
3
|
+
export const schema = {
|
|
4
|
+
eyebrow: RichTextField,
|
|
5
|
+
headline: RichTextField,
|
|
6
|
+
intro: RichTextField,
|
|
7
|
+
// Header labels for the two columns.
|
|
8
|
+
painLabel: RichTextField,
|
|
9
|
+
oursLabel: RichTextField,
|
|
10
|
+
// Each row pairs a "pain" statement with an "ours" answer.
|
|
11
|
+
rows: ListField({
|
|
12
|
+
pain: RichTextField,
|
|
13
|
+
ours: RichTextField,
|
|
14
|
+
}),
|
|
15
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
EditableRichText,
|
|
5
|
+
EditableText,
|
|
6
|
+
EditableLink,
|
|
7
|
+
EditableList,
|
|
8
|
+
read,
|
|
9
|
+
isSlotInPreview,
|
|
10
|
+
} from '@agntcms/next/client'
|
|
11
|
+
import type { EditableSlot, SlotItem } from '@agntcms/next/client'
|
|
12
|
+
import { hrefOf, isExternalLink } from '@agntcms/next'
|
|
13
|
+
import { schema } from './schema'
|
|
14
|
+
|
|
15
|
+
type Plan = SlotItem<typeof schema.plans.itemSchema>
|
|
16
|
+
|
|
17
|
+
interface Props {
|
|
18
|
+
readonly plans: EditableSlot<'list', ReadonlyArray<Plan>>
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function PricingPlansComponent({ plans }: Props) {
|
|
22
|
+
return (
|
|
23
|
+
<section className="bg-paper">
|
|
24
|
+
<div className="mx-auto w-full max-w-[1280px] px-8 py-24">
|
|
25
|
+
<EditableList
|
|
26
|
+
field={plans}
|
|
27
|
+
itemSchema={schema.plans.itemSchema}
|
|
28
|
+
className="grid grid-cols-1 gap-5 lg:grid-cols-3"
|
|
29
|
+
renderItem={(plan) => {
|
|
30
|
+
const featured = read(plan.featured)
|
|
31
|
+
const cta = read(plan.cta)
|
|
32
|
+
const ctaHref = hrefOf(cta)
|
|
33
|
+
const showCta = Boolean(ctaHref) || isSlotInPreview(plan.cta)
|
|
34
|
+
const borderClass = featured ? 'border-ink' : 'border-hairline'
|
|
35
|
+
const ctaStyles = featured
|
|
36
|
+
? 'bg-ink text-paper border-ink hover:bg-ink-2 hover:border-ink-2'
|
|
37
|
+
: 'bg-transparent text-ink border-hairline-2 hover:border-ink'
|
|
38
|
+
return (
|
|
39
|
+
<div className={`relative border bg-transparent p-8 ${borderClass}`}>
|
|
40
|
+
{featured ? (
|
|
41
|
+
<span className="absolute -top-px -left-px bg-ink px-2.5 py-[5px] font-mono text-[10px] font-medium tracking-[0.07em] uppercase text-paper">
|
|
42
|
+
most popular
|
|
43
|
+
</span>
|
|
44
|
+
) : null}
|
|
45
|
+
<EditableText
|
|
46
|
+
field={plan.name}
|
|
47
|
+
as="h3"
|
|
48
|
+
className={`m-0 font-mono text-[16px] font-medium tracking-[0.04em] uppercase text-ink-3 ${featured ? 'mt-5' : ''}`}
|
|
49
|
+
/>
|
|
50
|
+
<div className="mt-3.5 flex items-baseline gap-2">
|
|
51
|
+
<EditableText
|
|
52
|
+
field={plan.price}
|
|
53
|
+
as="span"
|
|
54
|
+
className="font-display text-[56px] font-semibold leading-none tracking-[-0.03em] text-ink"
|
|
55
|
+
/>
|
|
56
|
+
<EditableText
|
|
57
|
+
field={plan.priceSub}
|
|
58
|
+
as="span"
|
|
59
|
+
className="font-mono text-[13px] text-ink-3"
|
|
60
|
+
/>
|
|
61
|
+
</div>
|
|
62
|
+
<EditableRichText
|
|
63
|
+
field={plan.pitch}
|
|
64
|
+
className="mt-2.5 [&_p]:m-0 [&_p]:text-[14.5px] [&_p]:leading-[1.55] [&_p]:text-ink-3"
|
|
65
|
+
/>
|
|
66
|
+
{showCta && (
|
|
67
|
+
<a
|
|
68
|
+
href={ctaHref || '#'}
|
|
69
|
+
target={isExternalLink(cta) ? '_blank' : undefined}
|
|
70
|
+
rel={isExternalLink(cta) ? 'noreferrer' : undefined}
|
|
71
|
+
className="mt-6 block"
|
|
72
|
+
>
|
|
73
|
+
<EditableLink
|
|
74
|
+
field={plan.cta}
|
|
75
|
+
className={`inline-flex w-full items-center justify-center gap-2 whitespace-nowrap rounded-sm border px-[22px] py-[13px] text-[15px] font-medium no-underline transition-colors duration-200 ease-out ${ctaStyles}`}
|
|
76
|
+
/>
|
|
77
|
+
</a>
|
|
78
|
+
)}
|
|
79
|
+
<EditableList
|
|
80
|
+
field={plan.features}
|
|
81
|
+
itemSchema={schema.plans.itemSchema.features.itemSchema}
|
|
82
|
+
className="mt-7 flex list-none flex-col gap-2.5 border-t border-hairline p-0 pt-6"
|
|
83
|
+
renderItem={(f) => (
|
|
84
|
+
<div className="flex items-start gap-3 text-[14.5px] leading-[1.55] text-ink-2">
|
|
85
|
+
<span className="flex-shrink-0 font-mono text-ink-3">→</span>
|
|
86
|
+
<EditableRichText
|
|
87
|
+
field={f.item}
|
|
88
|
+
className="[&_p]:m-0 [&_p]:text-[14.5px] [&_p]:leading-[1.55] [&_p]:text-ink-2"
|
|
89
|
+
/>
|
|
90
|
+
</div>
|
|
91
|
+
)}
|
|
92
|
+
/>
|
|
93
|
+
</div>
|
|
94
|
+
)
|
|
95
|
+
}}
|
|
96
|
+
/>
|
|
97
|
+
</div>
|
|
98
|
+
</section>
|
|
99
|
+
)
|
|
100
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { defineSection } from '@agntcms/next'
|
|
2
|
+
import { schema } from './schema'
|
|
3
|
+
import { PricingPlansComponent } from './component'
|
|
4
|
+
|
|
5
|
+
export const PricingPlans = defineSection({
|
|
6
|
+
name: 'PricingPlans',
|
|
7
|
+
category: 'Pricing',
|
|
8
|
+
schema,
|
|
9
|
+
component: PricingPlansComponent,
|
|
10
|
+
})
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { RichTextField, TextField, LinkField, BooleanField, ListField } from '@agntcms/next'
|
|
2
|
+
|
|
3
|
+
export const schema = {
|
|
4
|
+
plans: ListField({
|
|
5
|
+
name: TextField,
|
|
6
|
+
price: TextField,
|
|
7
|
+
priceSub: TextField,
|
|
8
|
+
featured: BooleanField,
|
|
9
|
+
pitch: RichTextField,
|
|
10
|
+
features: ListField({ item: RichTextField }),
|
|
11
|
+
cta: LinkField,
|
|
12
|
+
}),
|
|
13
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { EditableRichText } from '@agntcms/next/client'
|
|
4
|
+
import type { EditableSlot } from '@agntcms/next/client'
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
readonly eyebrow: EditableSlot<'richText', string>
|
|
8
|
+
readonly headline: EditableSlot<'richText', string>
|
|
9
|
+
readonly body: EditableSlot<'richText', string>
|
|
10
|
+
readonly messageEyebrow: EditableSlot<'richText', string>
|
|
11
|
+
readonly message: EditableSlot<'richText', string>
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function ProblemComponent({ eyebrow, headline, body, messageEyebrow, message }: Props) {
|
|
15
|
+
return (
|
|
16
|
+
<section id="problem" className="bg-bg-primary">
|
|
17
|
+
<div className="mx-auto max-w-[1080px] px-8 py-16 border-t-[0.5px] border-border-secondary">
|
|
18
|
+
<EditableRichText
|
|
19
|
+
field={eyebrow}
|
|
20
|
+
className="prose mb-3.5 [&_p]:text-[11px] [&_p]:font-medium [&_p]:tracking-[0.10em] [&_p]:uppercase [&_p]:text-text-brand-primary [&_p]:m-0"
|
|
21
|
+
/>
|
|
22
|
+
<EditableRichText
|
|
23
|
+
field={headline}
|
|
24
|
+
className="mb-6
|
|
25
|
+
[&_h2]:font-display [&_h2]:font-medium [&_h2]:text-text-primary [&_h2]:m-0
|
|
26
|
+
[&_h2]:max-w-[22ch]
|
|
27
|
+
[&_h2]:!text-[clamp(28px,3.6vw,40px)] [&_h2]:!leading-[1.1] [&_h2]:!tracking-[-0.02em]"
|
|
28
|
+
/>
|
|
29
|
+
<EditableRichText
|
|
30
|
+
field={body}
|
|
31
|
+
className="prose max-w-[60ch]
|
|
32
|
+
[&_p]:text-text-primary [&_p]:m-0 [&_p]:mb-4 [&_p:last-child]:mb-0"
|
|
33
|
+
/>
|
|
34
|
+
<div className="bg-bg-secondary border-[0.5px] border-border-primary border-l-2 border-l-border-brand rounded-r-lg py-[18px] px-[22px] my-6 max-w-[60ch]">
|
|
35
|
+
<EditableRichText
|
|
36
|
+
field={messageEyebrow}
|
|
37
|
+
className="prose mb-2 [&_p]:text-[11px] [&_p]:font-medium [&_p]:tracking-[0.10em] [&_p]:uppercase [&_p]:text-text-brand-primary [&_p]:m-0"
|
|
38
|
+
/>
|
|
39
|
+
<EditableRichText
|
|
40
|
+
field={message}
|
|
41
|
+
className="prose
|
|
42
|
+
[&_p]:font-display [&_p]:font-medium [&_p]:text-[22px] [&_p]:leading-[1.35]
|
|
43
|
+
[&_p]:tracking-[-0.02em] [&_p]:text-text-primary [&_p]:m-0"
|
|
44
|
+
/>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
</section>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { defineSection } from '@agntcms/next'
|
|
2
|
+
import { schema } from './schema'
|
|
3
|
+
import { ProblemComponent } from './component'
|
|
4
|
+
|
|
5
|
+
export const Problem = defineSection({
|
|
6
|
+
name: 'Problem',
|
|
7
|
+
category: 'Content',
|
|
8
|
+
schema,
|
|
9
|
+
component: ProblemComponent,
|
|
10
|
+
})
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { RichTextField } from '@agntcms/next'
|
|
2
|
+
|
|
3
|
+
export const schema = {
|
|
4
|
+
eyebrow: RichTextField,
|
|
5
|
+
headline: RichTextField,
|
|
6
|
+
// Long-form prose rendered as a stack of paragraphs (markdown).
|
|
7
|
+
body: RichTextField,
|
|
8
|
+
// Eyebrow inside the highlight message block.
|
|
9
|
+
messageEyebrow: RichTextField,
|
|
10
|
+
// Highlight statement inside the teal-bordered message block.
|
|
11
|
+
message: RichTextField,
|
|
12
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { EditableText, EditableRichText, EditableList, read } from '@agntcms/next/client'
|
|
4
|
+
import { hrefOf, isExternalLink } from '@agntcms/next'
|
|
5
|
+
import type { EditableSlot, SlotItem } from '@agntcms/next/client'
|
|
6
|
+
import { schema } from './schema'
|
|
7
|
+
|
|
8
|
+
type Column = SlotItem<typeof schema.columns.itemSchema>
|
|
9
|
+
|
|
10
|
+
interface Props {
|
|
11
|
+
readonly brandName: EditableSlot<'text', string>
|
|
12
|
+
readonly showCaret: EditableSlot<'boolean', boolean>
|
|
13
|
+
readonly tagline: EditableSlot<'richText', string>
|
|
14
|
+
readonly columns: EditableSlot<'list', ReadonlyArray<Column>>
|
|
15
|
+
readonly copyright: EditableSlot<'text', string>
|
|
16
|
+
readonly versionLine: EditableSlot<'text', string>
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function SiteFooterComponent({
|
|
20
|
+
brandName,
|
|
21
|
+
showCaret,
|
|
22
|
+
tagline,
|
|
23
|
+
columns,
|
|
24
|
+
copyright,
|
|
25
|
+
versionLine,
|
|
26
|
+
}: Props) {
|
|
27
|
+
const caret = read(showCaret)
|
|
28
|
+
return (
|
|
29
|
+
<footer className="bg-paper">
|
|
30
|
+
<div className="mx-auto w-full max-w-[1280px] px-8 pt-20 pb-10">
|
|
31
|
+
<div className="grid grid-cols-1 gap-12 md:grid-cols-5">
|
|
32
|
+
<div className="md:col-span-2">
|
|
33
|
+
<span className="inline-flex items-center font-mono text-[18px] font-semibold tracking-[-0.01em] text-ink">
|
|
34
|
+
<EditableText field={brandName} as="span" />
|
|
35
|
+
{caret ? (
|
|
36
|
+
<span
|
|
37
|
+
aria-hidden="true"
|
|
38
|
+
className="ml-1 inline-block h-[14px] w-[7px] bg-teal animate-caret-blink"
|
|
39
|
+
/>
|
|
40
|
+
) : null}
|
|
41
|
+
</span>
|
|
42
|
+
<EditableRichText
|
|
43
|
+
field={tagline}
|
|
44
|
+
className="mt-3.5 max-w-[280px] [&_p]:m-0 [&_p]:text-sm [&_p]:leading-[1.6] [&_p]:text-ink-3"
|
|
45
|
+
/>
|
|
46
|
+
</div>
|
|
47
|
+
<EditableList
|
|
48
|
+
field={columns}
|
|
49
|
+
itemSchema={schema.columns.itemSchema}
|
|
50
|
+
className="contents"
|
|
51
|
+
renderItem={(col) => (
|
|
52
|
+
<div>
|
|
53
|
+
<EditableText
|
|
54
|
+
field={col.heading}
|
|
55
|
+
as="div"
|
|
56
|
+
className="mb-3 font-mono text-[11px] font-medium tracking-[0.07em] uppercase text-ink-3"
|
|
57
|
+
/>
|
|
58
|
+
<EditableList
|
|
59
|
+
field={col.links}
|
|
60
|
+
itemSchema={schema.columns.itemSchema.links.itemSchema}
|
|
61
|
+
className="flex flex-col gap-2"
|
|
62
|
+
renderItem={(item) => {
|
|
63
|
+
const link = read(item.link)
|
|
64
|
+
const href = hrefOf(link) || '#'
|
|
65
|
+
return (
|
|
66
|
+
<a
|
|
67
|
+
href={href}
|
|
68
|
+
target={isExternalLink(link) ? '_blank' : undefined}
|
|
69
|
+
rel={isExternalLink(link) ? 'noreferrer' : undefined}
|
|
70
|
+
className="text-sm text-ink-2 no-underline hover:text-ink"
|
|
71
|
+
>
|
|
72
|
+
<EditableText field={item.label} as="span" />
|
|
73
|
+
</a>
|
|
74
|
+
)
|
|
75
|
+
}}
|
|
76
|
+
/>
|
|
77
|
+
</div>
|
|
78
|
+
)}
|
|
79
|
+
/>
|
|
80
|
+
</div>
|
|
81
|
+
<div className="mt-16 flex flex-col gap-2 border-t border-hairline pt-6 font-mono text-xs text-ink-3 md:flex-row md:justify-between md:items-center">
|
|
82
|
+
<EditableText field={copyright} as="span" />
|
|
83
|
+
<EditableText field={versionLine} as="span" />
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
</footer>
|
|
87
|
+
)
|
|
88
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { defineSection } from '@agntcms/next'
|
|
2
|
+
import { schema } from './schema'
|
|
3
|
+
import { SiteFooterComponent } from './component'
|
|
4
|
+
|
|
5
|
+
export const SiteFooter = defineSection({
|
|
6
|
+
name: 'SiteFooter',
|
|
7
|
+
category: 'Layout',
|
|
8
|
+
schema,
|
|
9
|
+
component: SiteFooterComponent,
|
|
10
|
+
})
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { TextField, RichTextField, LinkField, BooleanField, ListField } from '@agntcms/next'
|
|
2
|
+
|
|
3
|
+
export const schema = {
|
|
4
|
+
brandName: TextField,
|
|
5
|
+
showCaret: BooleanField,
|
|
6
|
+
tagline: RichTextField,
|
|
7
|
+
columns: ListField({
|
|
8
|
+
heading: TextField,
|
|
9
|
+
links: ListField({ label: TextField, link: LinkField }),
|
|
10
|
+
}),
|
|
11
|
+
copyright: TextField,
|
|
12
|
+
versionLine: TextField,
|
|
13
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { EditableText, EditableLink, EditableList, read, isSlotInPreview } from '@agntcms/next/client'
|
|
4
|
+
import type { EditableSlot, SlotItem } from '@agntcms/next/client'
|
|
5
|
+
import { hrefOf, isExternalLink } from '@agntcms/next'
|
|
6
|
+
import type { LinkValue } from '@agntcms/next'
|
|
7
|
+
import { schema } from './schema'
|
|
8
|
+
|
|
9
|
+
type NavItem = SlotItem<typeof schema.navItems.itemSchema>
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
readonly brandName: EditableSlot<'text', string>
|
|
13
|
+
readonly showCaret: EditableSlot<'boolean', boolean>
|
|
14
|
+
readonly navItems: EditableSlot<'list', ReadonlyArray<NavItem>>
|
|
15
|
+
readonly signInCta: EditableSlot<'link', LinkValue>
|
|
16
|
+
readonly primaryCta: EditableSlot<'link', LinkValue>
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function HeaderCta({
|
|
20
|
+
field,
|
|
21
|
+
variant,
|
|
22
|
+
}: {
|
|
23
|
+
field: EditableSlot<'link', LinkValue>
|
|
24
|
+
variant: 'ghost' | 'primary'
|
|
25
|
+
}) {
|
|
26
|
+
const link = read(field)
|
|
27
|
+
const href = hrefOf(link)
|
|
28
|
+
const show = Boolean(href) || isSlotInPreview(field)
|
|
29
|
+
if (!show) return null
|
|
30
|
+
const styles =
|
|
31
|
+
variant === 'primary'
|
|
32
|
+
? 'bg-ink text-paper border-ink hover:bg-ink-2 hover:border-ink-2'
|
|
33
|
+
: 'bg-transparent text-ink border-transparent hover:text-ink-3'
|
|
34
|
+
return (
|
|
35
|
+
<a
|
|
36
|
+
href={href || '#'}
|
|
37
|
+
target={isExternalLink(link) ? '_blank' : undefined}
|
|
38
|
+
rel={isExternalLink(link) ? 'noreferrer' : undefined}
|
|
39
|
+
>
|
|
40
|
+
<EditableLink
|
|
41
|
+
field={field}
|
|
42
|
+
className={`inline-flex items-center gap-1.5 whitespace-nowrap rounded-sm border px-3 py-[7px] text-[13px] font-medium no-underline transition-colors duration-200 ease-out ${styles}`}
|
|
43
|
+
/>
|
|
44
|
+
</a>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function SiteHeaderComponent({
|
|
49
|
+
brandName,
|
|
50
|
+
showCaret,
|
|
51
|
+
navItems,
|
|
52
|
+
signInCta,
|
|
53
|
+
primaryCta,
|
|
54
|
+
}: Props) {
|
|
55
|
+
const caret = read(showCaret)
|
|
56
|
+
return (
|
|
57
|
+
<header className="sticky top-0 z-50 bg-paper">
|
|
58
|
+
<div className="mx-auto w-full max-w-[1280px] px-8">
|
|
59
|
+
<div className="flex items-center gap-7 py-[18px]">
|
|
60
|
+
<a
|
|
61
|
+
href="/"
|
|
62
|
+
aria-label="Home"
|
|
63
|
+
className="inline-flex items-center font-mono text-[18px] font-semibold tracking-[-0.01em] text-ink no-underline"
|
|
64
|
+
>
|
|
65
|
+
<EditableText field={brandName} as="span" />
|
|
66
|
+
{caret ? (
|
|
67
|
+
<span
|
|
68
|
+
aria-hidden="true"
|
|
69
|
+
className="ml-1 inline-block h-[14px] w-[7px] bg-teal animate-caret-blink"
|
|
70
|
+
/>
|
|
71
|
+
) : null}
|
|
72
|
+
</a>
|
|
73
|
+
<EditableList
|
|
74
|
+
field={navItems}
|
|
75
|
+
itemSchema={schema.navItems.itemSchema}
|
|
76
|
+
className="hidden md:flex items-center gap-[22px] ml-3"
|
|
77
|
+
renderItem={(item) => {
|
|
78
|
+
const link = read(item.link)
|
|
79
|
+
const href = hrefOf(link)
|
|
80
|
+
return (
|
|
81
|
+
<a
|
|
82
|
+
href={href || '#'}
|
|
83
|
+
target={isExternalLink(link) ? '_blank' : undefined}
|
|
84
|
+
rel={isExternalLink(link) ? 'noreferrer' : undefined}
|
|
85
|
+
className="border-b border-transparent pb-0.5 text-sm font-medium text-ink-3 no-underline hover:text-ink"
|
|
86
|
+
>
|
|
87
|
+
<EditableText field={item.label} as="span" />
|
|
88
|
+
</a>
|
|
89
|
+
)
|
|
90
|
+
}}
|
|
91
|
+
/>
|
|
92
|
+
<div className="flex-1" />
|
|
93
|
+
<HeaderCta field={signInCta} variant="ghost" />
|
|
94
|
+
<HeaderCta field={primaryCta} variant="primary" />
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
</header>
|
|
98
|
+
)
|
|
99
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { defineSection } from '@agntcms/next'
|
|
2
|
+
import { schema } from './schema'
|
|
3
|
+
import { SiteHeaderComponent } from './component'
|
|
4
|
+
|
|
5
|
+
export const SiteHeader = defineSection({
|
|
6
|
+
name: 'SiteHeader',
|
|
7
|
+
category: 'Layout',
|
|
8
|
+
schema,
|
|
9
|
+
component: SiteHeaderComponent,
|
|
10
|
+
})
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { TextField, LinkField, BooleanField, ListField } from '@agntcms/next'
|
|
2
|
+
|
|
3
|
+
export const schema = {
|
|
4
|
+
// Wordmark text (lowercase mono).
|
|
5
|
+
brandName: TextField,
|
|
6
|
+
// When true, render the teal blinking caret after the wordmark.
|
|
7
|
+
showCaret: BooleanField,
|
|
8
|
+
// Primary navigation.
|
|
9
|
+
navItems: ListField({ label: TextField, link: LinkField }),
|
|
10
|
+
// "Sign in"-style secondary header CTA.
|
|
11
|
+
signInCta: LinkField,
|
|
12
|
+
// "Start building"-style primary header CTA.
|
|
13
|
+
primaryCta: LinkField,
|
|
14
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
// SiteMeta is a data-only global: it holds SEO defaults (siteName,
|
|
4
|
+
// baseUrl, defaultOgImage, defaultDescription). It is consumed at the
|
|
5
|
+
// server layer by generateMetadata in app/[[...slug]]/page.tsx and by
|
|
6
|
+
// app/layout.tsx; it is never rendered as a visible section.
|
|
7
|
+
//
|
|
8
|
+
// The component is intentionally a no-op. GlobalSlot renders it when
|
|
9
|
+
// reading via runtime.getGlobal, but no visible output is wanted — the
|
|
10
|
+
// section exists purely to make the data editable through the admin UI.
|
|
11
|
+
|
|
12
|
+
import type { EditableSlot } from '@agntcms/next/client'
|
|
13
|
+
import type { ImageValue } from '@agntcms/next'
|
|
14
|
+
|
|
15
|
+
interface Props {
|
|
16
|
+
readonly siteName: EditableSlot<'text', string>
|
|
17
|
+
readonly baseUrl: EditableSlot<'text', string>
|
|
18
|
+
readonly defaultOgImage: EditableSlot<'image', ImageValue>
|
|
19
|
+
readonly defaultDescription: EditableSlot<'text', string>
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function SiteMetaComponent(_props: Props) {
|
|
23
|
+
// Intentional no-op — this global is consumed by server metadata
|
|
24
|
+
// helpers only. Rendering it in the page body would be incorrect.
|
|
25
|
+
return null
|
|
26
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { defineSection } from '@agntcms/next'
|
|
2
|
+
import { schema } from './schema'
|
|
3
|
+
import { SiteMetaComponent } from './component'
|
|
4
|
+
|
|
5
|
+
export const SiteMeta = defineSection({
|
|
6
|
+
name: 'SiteMeta',
|
|
7
|
+
// 'Global' category groups this section with SiteHeader/SiteFooter in the
|
|
8
|
+
// picker so authors understand it is a layout/infrastructure section rather
|
|
9
|
+
// than a page content block.
|
|
10
|
+
category: 'Global',
|
|
11
|
+
schema,
|
|
12
|
+
component: SiteMetaComponent,
|
|
13
|
+
})
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { TextField, ImageField } from '@agntcms/next'
|
|
2
|
+
|
|
3
|
+
export const schema = {
|
|
4
|
+
// Site name used in the page title template: "<page title> | <siteName>"
|
|
5
|
+
siteName: TextField,
|
|
6
|
+
// Canonical base URL, e.g. "https://agntcms.dev". Used by sitemap.ts
|
|
7
|
+
// and generateMetadata to construct canonical URLs. Include protocol,
|
|
8
|
+
// no trailing slash.
|
|
9
|
+
baseUrl: TextField,
|
|
10
|
+
// Fallback OG image shown when a page has no seo.ogImage and no
|
|
11
|
+
// coverImage. Optional — pages that always set their own OG image
|
|
12
|
+
// do not need this.
|
|
13
|
+
defaultOgImage: ImageField,
|
|
14
|
+
// Global fallback description. Shown on the home page (before any
|
|
15
|
+
// page-level seo.description is available) and used as openGraph
|
|
16
|
+
// description in layout.tsx.
|
|
17
|
+
defaultDescription: TextField,
|
|
18
|
+
}
|