@contractspec/bundle.marketing 3.8.7 → 3.8.9

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 (76) hide show
  1. package/.turbo/turbo-build.log +64 -32
  2. package/CHANGELOG.md +63 -0
  3. package/dist/browser/components/templates/TemplateCard.js +83 -0
  4. package/dist/browser/components/templates/TemplateCommandDialog.js +110 -0
  5. package/dist/browser/components/templates/TemplatePreviewContent.js +96 -0
  6. package/dist/browser/components/templates/TemplatesBrowseControls.js +115 -0
  7. package/dist/browser/components/templates/TemplatesCatalogSection.js +284 -0
  8. package/dist/browser/components/templates/TemplatesClientPage.js +840 -917
  9. package/dist/browser/components/templates/TemplatesHeroSection.js +87 -0
  10. package/dist/browser/components/templates/TemplatesNextStepsSection.js +126 -0
  11. package/dist/browser/components/templates/TemplatesPreviewModal.js +136 -126
  12. package/dist/browser/components/templates/index.js +873 -950
  13. package/dist/browser/components/templates/template-catalog.js +81 -0
  14. package/dist/browser/components/templates/template-new.js +23 -0
  15. package/dist/browser/components/templates/template-preview.js +43 -0
  16. package/dist/browser/components/templates/template-source.js +19 -0
  17. package/dist/browser/index.js +873 -950
  18. package/dist/components/templates/TemplateCard.d.ts +12 -0
  19. package/dist/components/templates/TemplateCard.js +78 -0
  20. package/dist/components/templates/TemplateCommandDialog.d.ts +6 -0
  21. package/dist/components/templates/TemplateCommandDialog.js +105 -0
  22. package/dist/components/templates/TemplatePreviewContent.d.ts +5 -0
  23. package/dist/components/templates/TemplatePreviewContent.js +91 -0
  24. package/dist/components/templates/TemplatesBrowseControls.d.ts +13 -0
  25. package/dist/components/templates/TemplatesBrowseControls.js +110 -0
  26. package/dist/components/templates/TemplatesCatalogSection.d.ts +14 -0
  27. package/dist/components/templates/TemplatesCatalogSection.js +279 -0
  28. package/dist/components/templates/TemplatesClientPage.js +840 -917
  29. package/dist/components/templates/TemplatesHeroSection.d.ts +5 -0
  30. package/dist/components/templates/TemplatesHeroSection.js +82 -0
  31. package/dist/components/templates/TemplatesNextStepsSection.d.ts +1 -0
  32. package/dist/components/templates/TemplatesNextStepsSection.js +121 -0
  33. package/dist/components/templates/TemplatesPreviewModal.d.ts +3 -4
  34. package/dist/components/templates/TemplatesPreviewModal.js +136 -126
  35. package/dist/components/templates/index.js +873 -950
  36. package/dist/components/templates/template-catalog.d.ts +27 -0
  37. package/dist/components/templates/template-catalog.js +76 -0
  38. package/dist/components/templates/template-catalog.test.d.ts +1 -0
  39. package/dist/components/templates/template-new.d.ts +2 -0
  40. package/dist/components/templates/template-new.js +18 -0
  41. package/dist/components/templates/template-preview.d.ts +18 -0
  42. package/dist/components/templates/template-preview.js +38 -0
  43. package/dist/components/templates/template-source.d.ts +3 -0
  44. package/dist/components/templates/template-source.js +14 -0
  45. package/dist/index.js +873 -950
  46. package/dist/node/components/templates/TemplateCard.js +78 -0
  47. package/dist/node/components/templates/TemplateCommandDialog.js +105 -0
  48. package/dist/node/components/templates/TemplatePreviewContent.js +91 -0
  49. package/dist/node/components/templates/TemplatesBrowseControls.js +110 -0
  50. package/dist/node/components/templates/TemplatesCatalogSection.js +279 -0
  51. package/dist/node/components/templates/TemplatesClientPage.js +840 -917
  52. package/dist/node/components/templates/TemplatesHeroSection.js +82 -0
  53. package/dist/node/components/templates/TemplatesNextStepsSection.js +121 -0
  54. package/dist/node/components/templates/TemplatesPreviewModal.js +136 -126
  55. package/dist/node/components/templates/index.js +873 -950
  56. package/dist/node/components/templates/template-catalog.js +76 -0
  57. package/dist/node/components/templates/template-new.js +18 -0
  58. package/dist/node/components/templates/template-preview.js +38 -0
  59. package/dist/node/components/templates/template-source.js +14 -0
  60. package/dist/node/index.js +873 -950
  61. package/package.json +185 -30
  62. package/src/components/templates/TemplateCard.tsx +74 -0
  63. package/src/components/templates/TemplateCommandDialog.tsx +92 -0
  64. package/src/components/templates/TemplatePreviewContent.tsx +182 -0
  65. package/src/components/templates/TemplatesBrowseControls.tsx +120 -0
  66. package/src/components/templates/TemplatesCatalogSection.tsx +166 -0
  67. package/src/components/templates/TemplatesClientPage.tsx +109 -741
  68. package/src/components/templates/TemplatesHeroSection.tsx +41 -0
  69. package/src/components/templates/TemplatesNextStepsSection.tsx +80 -0
  70. package/src/components/templates/TemplatesPreviewModal.tsx +19 -294
  71. package/src/components/templates/template-catalog.test.ts +66 -0
  72. package/src/components/templates/template-catalog.ts +132 -0
  73. package/src/components/templates/template-new.ts +12 -0
  74. package/src/components/templates/template-preview.ts +57 -0
  75. package/src/components/templates/template-source.ts +13 -0
  76. package/.turbo/turbo-prebuild.log +0 -1
@@ -4,12 +4,7 @@ import {
4
4
  analyticsEventNames,
5
5
  captureAnalyticsEvent,
6
6
  } from '@contractspec/bundle.library/libs/posthog/client';
7
- import type {
8
- RegistryTemplate,
9
- TemplateId,
10
- } from '@contractspec/lib.example-shared-ui';
11
7
  import { useRegistryTemplates } from '@contractspec/lib.example-shared-ui';
12
- import { cn } from '@contractspec/lib.ui-kit-core/utils';
13
8
  import {
14
9
  Dialog,
15
10
  DialogContent,
@@ -17,698 +12,124 @@ import {
17
12
  DialogHeader,
18
13
  DialogTitle,
19
14
  } from '@contractspec/lib.ui-kit-web/ui/dialog';
20
- import {
21
- Tooltip,
22
- TooltipContent,
23
- TooltipProvider,
24
- TooltipTrigger,
25
- } from '@contractspec/lib.ui-kit-web/ui/tooltip';
26
- import { getTemplate } from '@contractspec/module.examples';
27
- import { Search } from 'lucide-react';
28
- import Link from 'next/link';
29
- import { useState } from 'react';
15
+ import { useMemo, useState } from 'react';
30
16
  import { StudioSignupSection } from '../marketing';
17
+ import { TemplateCommandDialog } from './TemplateCommandDialog';
18
+ import { TemplatesBrowseControls } from './TemplatesBrowseControls';
19
+ import { TemplatesCatalogSection } from './TemplatesCatalogSection';
20
+ import { TemplatesHeroSection } from './TemplatesHeroSection';
21
+ import { TemplatesNextStepsSection } from './TemplatesNextStepsSection';
31
22
  import { TemplatePreviewModal } from './TemplatesPreviewModal';
23
+ import {
24
+ buildLocalTemplateCatalog,
25
+ matchesTemplateFilters,
26
+ } from './template-catalog';
27
+ import {
28
+ getAvailableTemplateSources,
29
+ isRegistryConfigured,
30
+ type TemplateSource,
31
+ } from './template-source';
32
32
 
33
- const templates = [
34
- {
35
- id: 'minimal-example',
36
- templateId: 'todos-app' as TemplateId,
37
- title: 'Minimal Example',
38
- description:
39
- 'A minimal template to get you running in minutes. Perfect for exploring the engine.',
40
- tags: ['Getting Started'],
41
- capabilities: 'Basic Forms, Auth',
42
- isStarter: true,
43
- previewUrl: '/sandbox?template=minimal-example',
44
- docsUrl: '/docs/getting-started/hello-world',
45
- },
46
- // ============================================
47
- // Phase 1 Examples (using cross-cutting modules)
48
- // ============================================
49
- {
50
- id: 'saas-boilerplate',
51
- templateId: 'saas-boilerplate' as TemplateId,
52
- title: 'SaaS Boilerplate',
53
- description:
54
- 'Complete SaaS foundation with multi-tenant orgs, projects, settings, and billing usage.',
55
- tags: ['Getting Started', 'SaaS', 'Business'],
56
- capabilities: 'Multi-tenancy, RBAC, Projects, Billing',
57
- isNew: true,
58
- previewUrl: '/sandbox?template=saas-boilerplate',
59
- docsUrl: '/docs/templates/saas-boilerplate',
60
- },
61
- {
62
- id: 'crm-pipeline',
63
- templateId: 'crm-pipeline' as TemplateId,
64
- title: 'CRM Pipeline',
65
- description:
66
- 'Sales CRM with contacts, companies, deals, pipeline stages, and task management.',
67
- tags: ['CRM', 'Business'],
68
- capabilities: 'Contacts, Deals, Pipelines, Tasks',
69
- isNew: true,
70
- previewUrl: '/sandbox?template=crm-pipeline',
71
- docsUrl: '/docs/templates/crm-pipeline',
72
- },
73
- {
74
- id: 'data-grid-showcase',
75
- templateId: 'data-grid-showcase' as TemplateId,
76
- title: 'Data Grid Showcase',
77
- description:
78
- 'Focused example for ContractSpec headless tables across client, server, and DataView-driven lanes.',
79
- tags: ['Ops', 'Business'],
80
- capabilities: 'Tables, Sorting, Pagination, Column Controls',
81
- isNew: true,
82
- previewUrl: '/sandbox?template=data-grid-showcase',
83
- docsUrl: '/docs/examples/data-grid-showcase',
84
- },
85
- {
86
- id: 'visualization-showcase',
87
- templateId: 'visualization-showcase' as TemplateId,
88
- title: 'Visualization Showcase',
89
- description:
90
- 'Canonical ContractSpec example for chart primitives, shared visualization contracts, and opinionated dashboard blocks.',
91
- tags: ['Ops', 'Visualization'],
92
- capabilities: 'Metrics, Charts, Timelines, Comparisons',
93
- isNew: true,
94
- previewUrl: '/sandbox?template=visualization-showcase',
95
- docsUrl: '/docs/examples/visualization-showcase',
96
- },
97
- {
98
- id: 'agent-console',
99
- templateId: 'agent-console' as TemplateId,
100
- title: 'AI Agent Console',
101
- description:
102
- 'AI agent orchestration platform with tools, agents, runs, and execution logs.',
103
- tags: ['AI', 'Ops'],
104
- capabilities: 'Tools, Agents, Runs, Metrics',
105
- isNew: true,
106
- previewUrl: '/sandbox?template=agent-console',
107
- docsUrl: '/docs/examples/agent-console',
108
- },
109
- {
110
- id: 'ai-chat-assistant',
111
- templateId: 'ai-chat-assistant' as TemplateId,
112
- title: 'AI Chat Assistant',
113
- description:
114
- 'Focused assistant template with reasoning, sources, suggestions, and MCP-aware tooling.',
115
- tags: ['AI', 'Ops'],
116
- capabilities: 'Chat UX, Sources, Suggestions, MCP',
117
- isNew: true,
118
- previewUrl: '/sandbox?template=ai-chat-assistant',
119
- docsUrl: '/docs/examples/ai-chat-assistant',
120
- },
121
- // ============================================
122
- // Phase 2-4 Examples
123
- // ============================================
124
- {
125
- id: 'workflow-system',
126
- templateId: 'workflow-system' as TemplateId,
127
- title: 'Workflow / Approval System',
128
- description:
129
- 'Multi-step workflows with role-based approvals and state transitions.',
130
- tags: ['Business', 'Ops'],
131
- capabilities: 'Workflows, Approvals, State Machine',
132
- isNew: true,
133
- previewUrl: '/sandbox?template=workflow-system',
134
- docsUrl: '/docs/examples/workflow-system',
135
- },
136
- {
137
- id: 'marketplace',
138
- templateId: 'marketplace' as TemplateId,
139
- title: 'Marketplace',
140
- description:
141
- 'Two-sided marketplace with stores, products, orders, and payouts.',
142
- tags: ['Business', 'Payments'],
143
- capabilities: 'Stores, Products, Orders, Payouts',
144
- isNew: true,
145
- previewUrl: '/sandbox?template=marketplace',
146
- docsUrl: '/docs/templates/marketplace',
147
- },
148
- {
149
- id: 'integration-hub',
150
- templateId: 'integration-hub' as TemplateId,
151
- title: 'Integration Hub',
152
- description:
153
- 'Third-party integrations with connections, sync configs, and field mapping.',
154
- tags: ['Ops', 'AI'],
155
- capabilities: 'Integrations, Connections, Sync',
156
- isNew: true,
157
- previewUrl: '/sandbox?template=integration-hub',
158
- docsUrl: '/docs/templates/integration-hub',
159
- },
160
- // ============================================
161
- // Learning Journeys
162
- // ============================================
163
- {
164
- id: 'learning-journey-studio-onboarding',
165
- templateId: 'learning-journey-studio-onboarding' as TemplateId,
166
- title: 'Learning Journey — Studio Getting Started',
167
- description:
168
- 'First 30 minutes in Studio: choose template, edit spec, regenerate, playground, evolution.',
169
- tags: ['Learning', 'Onboarding'],
170
- capabilities: 'Spec-first onboarding, XP/streak, progress widget',
171
- isNew: true,
172
- previewUrl: '/sandbox?template=learning-journey-studio-onboarding',
173
- docsUrl: '/docs/templates/learning-journey-studio-onboarding',
174
- },
175
- {
176
- id: 'learning-journey-platform-tour',
177
- templateId: 'learning-journey-platform-tour' as TemplateId,
178
- title: 'Learning Journey — Platform Primitives Tour',
179
- description:
180
- 'Touch identity, audit, notifications, jobs, flags, files, metering once with guided steps.',
181
- tags: ['Learning', 'Platform'],
182
- capabilities: 'Cross-module tour with event-driven completion',
183
- isNew: true,
184
- previewUrl: '/sandbox?template=learning-journey-platform-tour',
185
- docsUrl: '/docs/templates/learning-journey-platform-tour',
186
- },
187
- {
188
- id: 'learning-journey-crm-onboarding',
189
- templateId: 'learning-journey-crm-onboarding' as TemplateId,
190
- title: 'Learning Journey — CRM First Win',
191
- description:
192
- 'Get to first closed-won deal: pipeline, contact/company, deal, stages, follow-up.',
193
- tags: ['Learning', 'CRM'],
194
- capabilities: 'CRM onboarding with XP/streak/badge',
195
- isNew: true,
196
- previewUrl: '/sandbox?template=learning-journey-crm-onboarding',
197
- docsUrl: '/docs/templates/learning-journey-crm-onboarding',
198
- },
199
- {
200
- id: 'analytics-dashboard',
201
- templateId: 'analytics-dashboard' as TemplateId,
202
- title: 'Analytics Dashboard',
203
- description:
204
- 'Custom dashboards with widgets, saved queries, and real-time visualization.',
205
- tags: ['Business', 'Ops'],
206
- capabilities: 'Dashboards, Widgets, Queries',
207
- isNew: true,
208
- previewUrl: '/sandbox?template=analytics-dashboard',
209
- docsUrl: '/docs/templates/analytics-dashboard',
210
- },
211
- // ============================================
212
- // Classic Templates
213
- // ============================================
214
- {
215
- id: 'plumber-ops',
216
- templateId: 'messaging-app' as TemplateId,
217
- title: 'Plumber Ops',
218
- description:
219
- 'Complete workflow: Quotes → Deposit → Job → Invoice → Payment. Policy-enforced approvals.',
220
- tags: ['Trades', 'Payments'],
221
- capabilities: 'Quotes, Jobs, Invoicing, Payments',
222
- previewUrl: '/sandbox?template=plumber-ops',
223
- docsUrl: '/docs/specs/workflows',
224
- },
225
- {
226
- id: 'coliving-management',
227
- templateId: 'recipe-app-i18n' as TemplateId,
228
- title: 'Coliving Management',
229
- description:
230
- 'Coliving management: Onboarding, chores, shared wallet. Multi-party approvals built-in.',
231
- tags: ['Coliving', 'Finance'],
232
- capabilities: 'Tasks, Approvals, Payments',
233
- previewUrl: '/sandbox?template=coliving-management',
234
- docsUrl: '/docs/specs/workflows',
235
- },
236
- {
237
- id: 'chores-allowance',
238
- templateId: 'todos-app' as TemplateId,
239
- title: 'Chores & Allowance',
240
- description:
241
- 'Family task management with approval workflows. Teach financial accountability safely.',
242
- tags: ['Family', 'Ops'],
243
- capabilities: 'Tasks, Approvals, Notifications',
244
- previewUrl: '/sandbox?template=chores-allowance',
245
- docsUrl: '/docs/specs/workflows',
246
- },
247
- {
248
- id: 'service-dispatch',
249
- templateId: 'messaging-app' as TemplateId,
250
- title: 'Service Dispatch',
251
- description:
252
- 'Field service scheduling, routing, and invoicing. Real-time coordination with policy gates.',
253
- tags: ['Ops', 'Trades'],
254
- capabilities: 'Scheduling, Maps, Invoicing',
255
- previewUrl: '/sandbox?template=service-dispatch',
256
- docsUrl: '/docs/specs/workflows',
257
- },
258
- {
259
- id: 'content-review',
260
- templateId: 'todos-app' as TemplateId,
261
- title: 'Content Review',
262
- description:
263
- 'Multi-stage approval workflow for content. Audit trail for every decision.',
264
- tags: ['Ops'],
265
- capabilities: 'Workflows, Approvals, Comments',
266
- previewUrl: '/sandbox?template=content-review',
267
- docsUrl: '/docs/specs/workflows',
268
- },
269
- ];
270
- type LocalTemplate = (typeof templates)[number];
271
-
272
- const allTags = [
273
- 'Getting Started',
274
- 'SaaS',
275
- 'Business',
276
- 'CRM',
277
- 'AI',
278
- 'Trades',
279
- 'Coliving',
280
- 'Family',
281
- 'Ops',
282
- 'Payments',
283
- 'Learning',
284
- 'Platform',
285
- ];
33
+ const REGISTRY_URL = process.env.NEXT_PUBLIC_CONTRACTSPEC_REGISTRY_URL;
286
34
 
287
35
  export const TemplatesPage = () => {
288
36
  const [selectedTag, setSelectedTag] = useState<string | null>(null);
289
37
  const [search, setSearch] = useState('');
290
- const [preview, setPreview] = useState<TemplateId | null>(null);
38
+ const [previewTemplateId, setPreviewTemplateId] = useState<string | null>(
39
+ null
40
+ );
291
41
  const [studioSignupModalOpen, setStudioSignupModalOpen] = useState(false);
292
- const [source, setSource] = useState<'local' | 'registry'>('local');
293
- const [selectedTemplateForCommand, setSelectedTemplateForCommand] = useState<
294
- RegistryTemplate | LocalTemplate | null
295
- >(null);
42
+ const [selectedTemplateId, setSelectedTemplateId] = useState<string | null>(
43
+ null
44
+ );
45
+ const [source, setSource] = useState<TemplateSource>('local');
46
+
47
+ const registryConfigured = isRegistryConfigured(REGISTRY_URL);
48
+ const availableSources = getAvailableTemplateSources(REGISTRY_URL);
49
+ const localTemplates = useMemo(() => buildLocalTemplateCatalog(), []);
50
+ const localTemplateById = useMemo(
51
+ () => new Map(localTemplates.map((template) => [template.id, template])),
52
+ [localTemplates]
53
+ );
54
+ const availableTags = useMemo(
55
+ () =>
56
+ Array.from(
57
+ new Set(localTemplates.flatMap((template) => template.tags))
58
+ ).sort((left, right) => left.localeCompare(right)),
59
+ [localTemplates]
60
+ );
296
61
 
297
62
  const { data: registryTemplates = [], isLoading: registryLoading } =
298
63
  useRegistryTemplates();
299
64
 
300
- const filtered = templates.filter((t) => {
301
- const matchTag = !selectedTag || t.tags.includes(selectedTag);
302
- const matchSearch =
303
- !search ||
304
- t.title.toLowerCase().includes(search.toLowerCase()) ||
305
- t.description.toLowerCase().includes(search.toLowerCase());
306
- return matchTag && matchSearch;
307
- });
65
+ const filteredLocalTemplates = useMemo(
66
+ () =>
67
+ localTemplates.filter((template) =>
68
+ matchesTemplateFilters(template, search, selectedTag)
69
+ ),
70
+ [localTemplates, search, selectedTag]
71
+ );
308
72
 
309
- const commandId = selectedTemplateForCommand
310
- ? 'templateId' in selectedTemplateForCommand
311
- ? selectedTemplateForCommand.templateId
312
- : selectedTemplateForCommand.id
313
- : '';
73
+ const filteredRegistryTemplates = useMemo(
74
+ () =>
75
+ registryTemplates.filter((template) =>
76
+ matchesTemplateFilters(
77
+ {
78
+ title: template.name,
79
+ description: template.description,
80
+ tags: template.tags,
81
+ },
82
+ search,
83
+ selectedTag
84
+ )
85
+ ),
86
+ [registryTemplates, search, selectedTag]
87
+ );
314
88
 
315
89
  return (
316
- <TooltipProvider>
90
+ <>
317
91
  <main>
318
- <section className="section-padding hero-gradient border-border/70 border-b">
319
- <div className="editorial-shell space-y-8">
320
- <div className="max-w-4xl space-y-5">
321
- <p className="editorial-kicker">Proof through real scenarios</p>
322
- <h1 className="editorial-title">
323
- Templates that show the open system in practice.
324
- </h1>
325
- <p className="editorial-subtitle">
326
- These scenarios are the fastest way to understand ContractSpec:
327
- explicit contracts, aligned surfaces, and an adoption path from
328
- OSS exploration into Studio deployment.
329
- </p>
330
- </div>
331
- <div className="editorial-proof-strip">
332
- <div className="editorial-stat">
333
- <span className="editorial-stat-value">{templates.length}</span>
334
- <span className="editorial-label">curated scenarios</span>
335
- </div>
336
- <div className="editorial-stat">
337
- <span className="editorial-stat-value">2</span>
338
- <span className="editorial-label">entry paths</span>
339
- </div>
340
- <div className="editorial-stat">
341
- <span className="editorial-stat-value">OSS</span>
342
- <span className="editorial-label">first, Studio second</span>
343
- </div>
344
- </div>
345
- </div>
346
- </section>
347
-
348
- <section className="editorial-section">
349
- <div className="editorial-shell space-y-6">
350
- <div className="flex flex-col gap-6 lg:flex-row lg:items-end lg:justify-between">
351
- <div className="max-w-3xl space-y-3">
352
- <p className="editorial-kicker">Browse by source</p>
353
- <h2 className="font-serif text-4xl tracking-[-0.04em]">
354
- Use local scenarios for core proof, then scan the community.
355
- </h2>
356
- <p className="text-muted-foreground text-sm leading-7">
357
- Local templates show the official adoption path. Community
358
- templates show where the ecosystem is pushing the system next.
359
- </p>
360
- </div>
361
- <div className="flex gap-2">
362
- <button
363
- onClick={() => setSource('local')}
364
- className={cn(
365
- 'rounded-full px-4 py-2 font-medium text-sm transition-colors',
366
- {
367
- 'bg-primary text-primary-foreground': source === 'local',
368
- 'border border-border bg-card hover:bg-card/80':
369
- source !== 'local',
370
- }
371
- )}
372
- aria-pressed={source === 'local'}
373
- >
374
- Local
375
- </button>
376
- <button
377
- onClick={() => setSource('registry')}
378
- className={cn(
379
- 'rounded-full px-4 py-2 font-medium text-sm transition-colors',
380
- {
381
- 'bg-primary text-primary-foreground':
382
- source === 'registry',
383
- 'border border-border bg-card hover:bg-card/80':
384
- source !== 'registry',
385
- }
386
- )}
387
- aria-pressed={source === 'registry'}
388
- >
389
- Community
390
- </button>
391
- </div>
392
- </div>
393
- <div className="editorial-panel space-y-5">
394
- <div className="relative">
395
- <Search
396
- className="absolute top-3.5 left-4 text-muted-foreground"
397
- size={18}
398
- />
399
- <input
400
- type="text"
401
- placeholder="Search scenarios, industries, or capabilities"
402
- value={search}
403
- onChange={(e) => setSearch(e.target.value)}
404
- className="w-full rounded-full border border-border bg-background px-12 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
405
- aria-label="Search templates"
406
- />
407
- </div>
408
- <div className="flex flex-wrap gap-2">
409
- <button
410
- onClick={() => setSelectedTag(null)}
411
- className={cn(
412
- 'rounded-full px-4 py-2 font-medium text-sm transition-colors',
413
- {
414
- 'bg-primary text-primary-foreground':
415
- selectedTag === null,
416
- 'border border-border bg-card hover:bg-card/80':
417
- selectedTag !== null,
418
- }
419
- )}
420
- aria-pressed={selectedTag === null}
421
- >
422
- All
423
- </button>
424
- {allTags.map((tag) => (
425
- <button
426
- key={tag}
427
- onClick={() => setSelectedTag(tag)}
428
- className={cn(
429
- 'rounded-full px-4 py-2 font-medium text-sm transition-colors',
430
- {
431
- 'bg-primary text-primary-foreground':
432
- selectedTag === tag,
433
- 'border border-border bg-card hover:bg-card/80':
434
- selectedTag !== tag,
435
- }
436
- )}
437
- aria-pressed={selectedTag === tag}
438
- >
439
- {tag}
440
- </button>
441
- ))}
442
- </div>
443
- </div>
444
- </div>
445
- </section>
446
-
447
- <section className="section-padding">
448
- <div className="editorial-shell">
449
- {source === 'registry' ? (
450
- registryLoading ? (
451
- <div className="py-12 text-center">
452
- <p className="text-muted-foreground">
453
- Loading community templates…
454
- </p>
455
- </div>
456
- ) : registryTemplates.length === 0 ? (
457
- <div className="py-12 text-center">
458
- <p className="text-muted-foreground">
459
- No community templates found (configure
460
- `NEXT_PUBLIC_CONTRACTSPEC_REGISTRY_URL`).
461
- </p>
462
- </div>
463
- ) : (
464
- <div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
465
- {registryTemplates.map((t) => (
466
- <div
467
- key={t.id}
468
- className="editorial-panel relative flex flex-col space-y-4 transition-colors hover:border-[color:rgb(162_79_42_/_0.55)]"
469
- >
470
- <div>
471
- <h3 className="font-serif text-2xl tracking-[-0.03em]">
472
- {t.name}
473
- </h3>
474
- <p className="mt-1 text-muted-foreground text-sm">
475
- {t.description}
476
- </p>
477
- </div>
478
- <div className="flex-1 space-y-2">
479
- <div className="flex flex-wrap gap-1">
480
- {t.tags.map((tag) => (
481
- <span
482
- key={tag}
483
- className="rounded-full border border-border bg-muted px-3 py-1 text-[11px] text-muted-foreground"
484
- >
485
- {tag}
486
- </span>
487
- ))}
488
- </div>
489
- </div>
490
- <div className="flex gap-2 pt-4">
491
- <Tooltip>
492
- <TooltipTrigger asChild>
493
- <button
494
- className="btn-ghost flex-1 text-center text-xs"
495
- onClick={() => {
496
- const local = getTemplate(t.id as TemplateId);
497
- if (!local) {
498
- setSelectedTemplateForCommand(t);
499
- return;
500
- }
501
- setPreview(t.id as TemplateId);
502
- }}
503
- >
504
- Preview
505
- </button>
506
- </TooltipTrigger>
507
- <TooltipContent>
508
- <p>Preview this template (if available locally)</p>
509
- </TooltipContent>
510
- </Tooltip>
511
- <Tooltip>
512
- <TooltipTrigger asChild>
513
- <button
514
- className="btn-primary flex-1 text-center text-xs"
515
- onClick={() => {
516
- captureAnalyticsEvent(
517
- analyticsEventNames.EXAMPLE_REPO_OPEN,
518
- {
519
- surface: 'templates',
520
- templateId: t.id,
521
- source: 'registry',
522
- }
523
- );
524
- setSelectedTemplateForCommand(t);
525
- }}
526
- >
527
- Use Template
528
- </button>
529
- </TooltipTrigger>
530
- <TooltipContent>
531
- <p>Get CLI command</p>
532
- </TooltipContent>
533
- </Tooltip>
534
- </div>
535
- </div>
536
- ))}
537
- </div>
538
- )
539
- ) : filtered.length === 0 ? (
540
- <div className="py-12 text-center">
541
- <p className="text-muted-foreground">
542
- No templates match your filters. Try a different search.
543
- </p>
544
- </div>
545
- ) : (
546
- <div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
547
- {filtered.map((template, i) => (
548
- <div
549
- key={i}
550
- className="editorial-panel relative flex flex-col space-y-4 transition-colors hover:border-[color:rgb(162_79_42_/_0.55)]"
551
- >
552
- {'isNew' in template && template.isNew && (
553
- <span className="absolute top-4 right-4 rounded-full bg-[color:var(--success)] px-2.5 py-1 font-medium text-[11px] text-white uppercase">
554
- New
555
- </span>
556
- )}
557
- <div>
558
- <h3 className="font-serif text-2xl tracking-[-0.03em]">
559
- {template.title}
560
- </h3>
561
- <p className="mt-1 text-muted-foreground text-sm">
562
- {template.description}
563
- </p>
564
- </div>
565
- <div className="flex-1 space-y-2">
566
- <p className="text-muted-foreground text-xs">
567
- <span className="font-medium text-foreground">
568
- Capabilities:
569
- </span>{' '}
570
- {template.capabilities}
571
- </p>
572
- <div className="flex flex-wrap gap-1">
573
- {template.tags.map((tag) => (
574
- <span
575
- key={tag}
576
- className="rounded-full border border-border bg-muted px-3 py-1 text-[11px] text-muted-foreground"
577
- >
578
- {tag}
579
- </span>
580
- ))}
581
- </div>
582
- </div>
583
- <div className="flex gap-2 pt-4">
584
- <Tooltip>
585
- <TooltipTrigger asChild>
586
- <button
587
- className="btn-ghost flex-1 text-center text-xs"
588
- onClick={() => setPreview(template.templateId)}
589
- >
590
- Preview
591
- </button>
592
- </TooltipTrigger>
593
- <TooltipContent>
594
- <p>Preview this template in a modal</p>
595
- </TooltipContent>
596
- </Tooltip>
597
- <Tooltip>
598
- <TooltipTrigger asChild>
599
- <button
600
- className="btn-primary flex-1 text-center text-xs"
601
- onClick={() => {
602
- captureAnalyticsEvent(
603
- analyticsEventNames.EXAMPLE_REPO_OPEN,
604
- {
605
- surface: 'templates',
606
- templateId: template.templateId,
607
- source: 'local',
608
- }
609
- );
610
- setSelectedTemplateForCommand(template);
611
- }}
612
- >
613
- Use Template
614
- </button>
615
- </TooltipTrigger>
616
- <TooltipContent>
617
- <p>Get CLI command</p>
618
- </TooltipContent>
619
- </Tooltip>
620
- </div>
621
- </div>
622
- ))}
623
- </div>
624
- )}
625
- </div>
626
- </section>
627
-
628
- <section className="editorial-section bg-striped">
629
- <div className="editorial-shell space-y-8">
630
- <div className="max-w-3xl space-y-4">
631
- <p className="editorial-kicker">From template to real system</p>
632
- <h2 className="font-serif text-4xl tracking-[-0.04em] md:text-5xl">
633
- Templates become useful when the system can absorb more context.
634
- </h2>
635
- <p className="editorial-copy">
636
- Use templates to prove the base flow, then layer integrations,
637
- knowledge, and runtime behavior on top without losing the same
638
- contract source.
639
- </p>
640
- </div>
641
- <div className="grid gap-6 md:grid-cols-3">
642
- <div className="editorial-panel space-y-4">
643
- <div className="text-3xl">💳</div>
644
- <h3 className="font-serif text-2xl tracking-[-0.03em]">
645
- Add payments
646
- </h3>
647
- <p className="text-muted-foreground text-sm">
648
- Connect Stripe to any template for payment processing,
649
- subscriptions, and invoicing. Type-safe and policy-enforced.
650
- </p>
651
- <Link
652
- href="/docs/integrations/stripe"
653
- className="font-medium text-[color:var(--blue)] text-sm hover:opacity-80"
654
- >
655
- Learn more →
656
- </Link>
657
- </div>
658
- <div className="editorial-panel space-y-4">
659
- <div className="text-3xl">📧</div>
660
- <h3 className="font-serif text-2xl tracking-[-0.03em]">
661
- Add notifications
662
- </h3>
663
- <p className="text-muted-foreground text-sm">
664
- Send transactional emails via Postmark or Resend. Process
665
- inbound emails with Gmail API. SMS via Twilio.
666
- </p>
667
- <Link
668
- href="/docs/integrations"
669
- className="font-medium text-[color:var(--blue)] text-sm hover:opacity-80"
670
- >
671
- View integrations →
672
- </Link>
673
- </div>
674
- <div className="editorial-panel space-y-4">
675
- <div className="text-3xl">🧠</div>
676
- <h3 className="font-serif text-2xl tracking-[-0.03em]">
677
- Add AI and knowledge
678
- </h3>
679
- <p className="text-muted-foreground text-sm">
680
- Power templates with OpenAI, vector search via Qdrant, and
681
- structured knowledge spaces for context-aware workflows.
682
- </p>
683
- <Link
684
- href="/docs/knowledge"
685
- className="font-medium text-[color:var(--blue)] text-sm hover:opacity-80"
686
- >
687
- Learn about knowledge →
688
- </Link>
689
- </div>
690
- </div>
691
- <div className="pt-4 text-center">
692
- <p className="mb-4 text-muted-foreground text-sm">
693
- All integrations are configured per-tenant with automatic health
694
- checks and credential rotation.
695
- </p>
696
- <Link href="/docs/architecture" className="btn-primary">
697
- View Architecture
698
- </Link>
699
- </div>
700
- </div>
701
- </section>
92
+ <TemplatesHeroSection
93
+ localTemplateCount={localTemplates.length}
94
+ sourceCount={availableSources.length}
95
+ />
96
+ <TemplatesBrowseControls
97
+ registryConfigured={registryConfigured}
98
+ availableSources={availableSources}
99
+ source={source}
100
+ onSourceChange={setSource}
101
+ search={search}
102
+ onSearchChange={setSearch}
103
+ selectedTag={selectedTag}
104
+ onTagChange={setSelectedTag}
105
+ availableTags={availableTags}
106
+ />
107
+ <TemplatesCatalogSection
108
+ source={source}
109
+ registryConfigured={registryConfigured}
110
+ registryLoading={registryLoading}
111
+ localTemplates={filteredLocalTemplates}
112
+ registryTemplates={filteredRegistryTemplates}
113
+ localTemplateById={localTemplateById}
114
+ onPreview={setPreviewTemplateId}
115
+ onUseTemplate={(templateId, templateSource) => {
116
+ captureAnalyticsEvent(analyticsEventNames.EXAMPLE_REPO_OPEN, {
117
+ surface: 'templates',
118
+ templateId,
119
+ source: templateSource,
120
+ });
121
+ setSelectedTemplateId(templateId);
122
+ }}
123
+ />
124
+ <TemplatesNextStepsSection />
702
125
  </main>
703
126
 
704
- {/*{preview ? (*/}
705
- <TemplatePreviewModal
706
- templateId={preview}
707
- onClose={() => {
708
- setPreview(null);
709
- }}
710
- />
711
- {/*) : null}*/}
127
+ {previewTemplateId ? (
128
+ <TemplatePreviewModal
129
+ templateId={previewTemplateId}
130
+ onClose={() => setPreviewTemplateId(null)}
131
+ />
132
+ ) : null}
712
133
 
713
134
  <Dialog
714
135
  open={studioSignupModalOpen}
@@ -726,67 +147,14 @@ export const TemplatesPage = () => {
726
147
  </DialogContent>
727
148
  </Dialog>
728
149
 
729
- <Dialog
730
- open={!!selectedTemplateForCommand}
731
- onOpenChange={() => setSelectedTemplateForCommand(null)}
732
- >
733
- <DialogContent className="max-w-md">
734
- <DialogHeader>
735
- <DialogTitle>Use this template</DialogTitle>
736
- <DialogDescription>
737
- Initialize a new project with this template using the CLI.
738
- </DialogDescription>
739
- </DialogHeader>
740
- <div className="space-y-4 pt-4">
741
- <div className="rounded-md border border-zinc-800 bg-zinc-950 p-4 font-mono text-sm text-zinc-50">
742
- npx contractspec init --template {commandId}
743
- </div>
744
- <div className="flex gap-2">
745
- <button
746
- className="btn-secondary w-full"
747
- onClick={() => {
748
- navigator.clipboard.writeText(
749
- `npx contractspec init --template ${commandId}`
750
- );
751
- captureAnalyticsEvent(
752
- analyticsEventNames.COPY_COMMAND_CLICK,
753
- {
754
- surface: 'templates',
755
- templateId: commandId,
756
- filename: 'templates-cli',
757
- }
758
- );
759
- }}
760
- >
761
- Copy Command
762
- </button>
763
- </div>
764
- <div className="relative">
765
- <div className="absolute inset-0 flex items-center">
766
- <span className="w-full border-border border-t" />
767
- </div>
768
- <div className="relative flex justify-center text-xs uppercase">
769
- <span className="bg-background px-2 text-muted-foreground">
770
- Or
771
- </span>
772
- </div>
773
- </div>
774
- <button
775
- className="btn-ghost w-full text-sm"
776
- onClick={() => {
777
- captureAnalyticsEvent(analyticsEventNames.CTA_STUDIO_CLICK, {
778
- surface: 'templates',
779
- templateId: commandId,
780
- });
781
- setSelectedTemplateForCommand(null);
782
- setStudioSignupModalOpen(true);
783
- }}
784
- >
785
- Deploy to Studio
786
- </button>
787
- </div>
788
- </DialogContent>
789
- </Dialog>
790
- </TooltipProvider>
150
+ <TemplateCommandDialog
151
+ templateId={selectedTemplateId}
152
+ onClose={() => setSelectedTemplateId(null)}
153
+ onDeployStudio={() => {
154
+ setSelectedTemplateId(null);
155
+ setStudioSignupModalOpen(true);
156
+ }}
157
+ />
158
+ </>
791
159
  );
792
160
  };