@contractspec/bundle.marketing 3.8.8 → 3.8.10

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 (96) hide show
  1. package/.turbo/turbo-build.log +73 -29
  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 +130 -0
  7. package/dist/browser/components/templates/TemplatesCatalogSection.js +307 -0
  8. package/dist/browser/components/templates/TemplatesClientPage.js +1020 -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/TemplatesOverlays.js +2874 -0
  12. package/dist/browser/components/templates/TemplatesPreviewModal.js +136 -126
  13. package/dist/browser/components/templates/index.js +1055 -952
  14. package/dist/browser/components/templates/template-catalog.js +83 -0
  15. package/dist/browser/components/templates/template-filters.js +99 -0
  16. package/dist/browser/components/templates/template-new.js +23 -0
  17. package/dist/browser/components/templates/template-preview.js +43 -0
  18. package/dist/browser/components/templates/template-source.js +19 -0
  19. package/dist/browser/components/templates/template-tag-visibility.js +40 -0
  20. package/dist/browser/components/templates/useTemplateBrowseState.js +191 -0
  21. package/dist/browser/index.js +1055 -952
  22. package/dist/components/templates/TemplateCard.d.ts +12 -0
  23. package/dist/components/templates/TemplateCard.js +78 -0
  24. package/dist/components/templates/TemplateCommandDialog.d.ts +6 -0
  25. package/dist/components/templates/TemplateCommandDialog.js +105 -0
  26. package/dist/components/templates/TemplatePreviewContent.d.ts +5 -0
  27. package/dist/components/templates/TemplatePreviewContent.js +91 -0
  28. package/dist/components/templates/TemplatesBrowseControls.d.ts +18 -0
  29. package/dist/components/templates/TemplatesBrowseControls.js +125 -0
  30. package/dist/components/templates/TemplatesCatalogSection.d.ts +17 -0
  31. package/dist/components/templates/TemplatesCatalogSection.js +302 -0
  32. package/dist/components/templates/TemplatesClientPage.js +1020 -917
  33. package/dist/components/templates/TemplatesHeroSection.d.ts +5 -0
  34. package/dist/components/templates/TemplatesHeroSection.js +82 -0
  35. package/dist/components/templates/TemplatesNextStepsSection.d.ts +1 -0
  36. package/dist/components/templates/TemplatesNextStepsSection.js +121 -0
  37. package/dist/components/templates/TemplatesOverlays.d.ts +10 -0
  38. package/dist/components/templates/TemplatesOverlays.js +2869 -0
  39. package/dist/components/templates/TemplatesPreviewModal.d.ts +3 -4
  40. package/dist/components/templates/TemplatesPreviewModal.js +136 -126
  41. package/dist/components/templates/index.js +1055 -952
  42. package/dist/components/templates/template-catalog.d.ts +28 -0
  43. package/dist/components/templates/template-catalog.js +78 -0
  44. package/dist/components/templates/template-catalog.test.d.ts +1 -0
  45. package/dist/components/templates/template-filters.d.ts +12 -0
  46. package/dist/components/templates/template-filters.js +94 -0
  47. package/dist/components/templates/template-new.d.ts +2 -0
  48. package/dist/components/templates/template-new.js +18 -0
  49. package/dist/components/templates/template-preview.d.ts +18 -0
  50. package/dist/components/templates/template-preview.js +38 -0
  51. package/dist/components/templates/template-source.d.ts +3 -0
  52. package/dist/components/templates/template-source.js +14 -0
  53. package/dist/components/templates/template-tag-visibility.d.ts +10 -0
  54. package/dist/components/templates/template-tag-visibility.js +35 -0
  55. package/dist/components/templates/useTemplateBrowseState.d.ts +22 -0
  56. package/dist/components/templates/useTemplateBrowseState.js +186 -0
  57. package/dist/index.js +1055 -952
  58. package/dist/node/components/templates/TemplateCard.js +78 -0
  59. package/dist/node/components/templates/TemplateCommandDialog.js +105 -0
  60. package/dist/node/components/templates/TemplatePreviewContent.js +91 -0
  61. package/dist/node/components/templates/TemplatesBrowseControls.js +125 -0
  62. package/dist/node/components/templates/TemplatesCatalogSection.js +302 -0
  63. package/dist/node/components/templates/TemplatesClientPage.js +1020 -917
  64. package/dist/node/components/templates/TemplatesHeroSection.js +82 -0
  65. package/dist/node/components/templates/TemplatesNextStepsSection.js +121 -0
  66. package/dist/node/components/templates/TemplatesOverlays.js +2869 -0
  67. package/dist/node/components/templates/TemplatesPreviewModal.js +136 -126
  68. package/dist/node/components/templates/index.js +1055 -952
  69. package/dist/node/components/templates/template-catalog.js +78 -0
  70. package/dist/node/components/templates/template-filters.js +94 -0
  71. package/dist/node/components/templates/template-new.js +18 -0
  72. package/dist/node/components/templates/template-preview.js +38 -0
  73. package/dist/node/components/templates/template-source.js +14 -0
  74. package/dist/node/components/templates/template-tag-visibility.js +35 -0
  75. package/dist/node/components/templates/useTemplateBrowseState.js +186 -0
  76. package/dist/node/index.js +1055 -952
  77. package/package.json +237 -26
  78. package/src/components/templates/TemplateCard.tsx +74 -0
  79. package/src/components/templates/TemplateCommandDialog.tsx +92 -0
  80. package/src/components/templates/TemplatePreviewContent.tsx +182 -0
  81. package/src/components/templates/TemplatesBrowseControls.tsx +144 -0
  82. package/src/components/templates/TemplatesCatalogSection.tsx +191 -0
  83. package/src/components/templates/TemplatesClientPage.tsx +85 -773
  84. package/src/components/templates/TemplatesHeroSection.tsx +41 -0
  85. package/src/components/templates/TemplatesNextStepsSection.tsx +80 -0
  86. package/src/components/templates/TemplatesOverlays.tsx +65 -0
  87. package/src/components/templates/TemplatesPreviewModal.tsx +19 -294
  88. package/src/components/templates/template-catalog.test.ts +162 -0
  89. package/src/components/templates/template-catalog.ts +140 -0
  90. package/src/components/templates/template-filters.ts +57 -0
  91. package/src/components/templates/template-new.ts +12 -0
  92. package/src/components/templates/template-preview.ts +57 -0
  93. package/src/components/templates/template-source.ts +13 -0
  94. package/src/components/templates/template-tag-visibility.ts +58 -0
  95. package/src/components/templates/useTemplateBrowseState.ts +101 -0
  96. package/.turbo/turbo-prebuild.log +0 -1
@@ -0,0 +1,182 @@
1
+ 'use client';
2
+
3
+ import {
4
+ type TemplateId,
5
+ TemplateShell,
6
+ } from '@contractspec/lib.example-shared-ui';
7
+ import { LoadingSpinner } from '@contractspec/lib.ui-kit-web/ui/atoms/LoadingSpinner';
8
+ import dynamic from 'next/dynamic';
9
+ import type { ComponentType } from 'react';
10
+
11
+ const SaasDashboard = dynamic(
12
+ () =>
13
+ import('@contractspec/example.saas-boilerplate').then(
14
+ (module) => module.SaasDashboard
15
+ ),
16
+ { ssr: false, loading: () => <LoadingSpinner /> }
17
+ );
18
+
19
+ const CrmDashboard = dynamic(
20
+ () =>
21
+ import('@contractspec/example.crm-pipeline').then(
22
+ (module) => module.CrmDashboard
23
+ ),
24
+ { ssr: false, loading: () => <LoadingSpinner /> }
25
+ );
26
+
27
+ const DataGridShowcase = dynamic(
28
+ () =>
29
+ import('@contractspec/example.data-grid-showcase/ui').then(
30
+ (module) => module.DataGridShowcase
31
+ ),
32
+ { ssr: false, loading: () => <LoadingSpinner /> }
33
+ );
34
+
35
+ const VisualizationShowcase = dynamic(
36
+ () =>
37
+ import('@contractspec/example.visualization-showcase/ui').then(
38
+ (module) => module.VisualizationShowcase
39
+ ),
40
+ { ssr: false, loading: () => <LoadingSpinner /> }
41
+ );
42
+
43
+ const AgentDashboard = dynamic(
44
+ () =>
45
+ import('@contractspec/example.agent-console/ui').then(
46
+ (module) => module.AgentDashboard
47
+ ),
48
+ { ssr: false, loading: () => <LoadingSpinner /> }
49
+ );
50
+
51
+ const AiChatAssistantDashboard = dynamic(
52
+ () =>
53
+ import('@contractspec/example.ai-chat-assistant').then(
54
+ (module) => module.AiChatAssistantDashboard
55
+ ),
56
+ { ssr: false, loading: () => <LoadingSpinner /> }
57
+ );
58
+
59
+ const WorkflowDashboard = dynamic(
60
+ () =>
61
+ import('@contractspec/example.workflow-system/ui').then(
62
+ (module) => module.WorkflowDashboard
63
+ ),
64
+ { ssr: false, loading: () => <LoadingSpinner /> }
65
+ );
66
+
67
+ const MarketplaceDashboard = dynamic(
68
+ () =>
69
+ import('@contractspec/example.marketplace/ui').then(
70
+ (module) => module.MarketplaceDashboard
71
+ ),
72
+ { ssr: false, loading: () => <LoadingSpinner /> }
73
+ );
74
+
75
+ const IntegrationDashboard = dynamic(
76
+ () =>
77
+ import('@contractspec/example.integration-hub/ui').then(
78
+ (module) => module.IntegrationDashboard
79
+ ),
80
+ { ssr: false, loading: () => <LoadingSpinner /> }
81
+ );
82
+
83
+ const AnalyticsDashboard = dynamic(
84
+ () =>
85
+ import('@contractspec/example.analytics-dashboard').then(
86
+ (module) => module.AnalyticsDashboard
87
+ ),
88
+ { ssr: false, loading: () => <LoadingSpinner /> }
89
+ );
90
+
91
+ interface PreviewDefinition {
92
+ title: string;
93
+ description: string;
94
+ component: ComponentType;
95
+ }
96
+
97
+ const PREVIEW_DEFINITIONS: Partial<Record<TemplateId, PreviewDefinition>> = {
98
+ 'saas-boilerplate': {
99
+ title: 'SaaS Boilerplate',
100
+ description:
101
+ 'Multi-tenant organizations, projects, settings, and billing usage tracking.',
102
+ component: SaasDashboard,
103
+ },
104
+ 'crm-pipeline': {
105
+ title: 'CRM Pipeline',
106
+ description:
107
+ 'Sales CRM with contacts, companies, deals, and pipeline stages.',
108
+ component: CrmDashboard,
109
+ },
110
+ 'data-grid-showcase': {
111
+ title: 'Data Grid Showcase',
112
+ description:
113
+ 'Shared ContractSpec table primitives with client, server, and DataView-driven lanes.',
114
+ component: DataGridShowcase,
115
+ },
116
+ 'visualization-showcase': {
117
+ title: 'Visualization Showcase',
118
+ description:
119
+ 'ContractSpec-owned chart primitives rendered through shared visualization contracts and design-system wrappers.',
120
+ component: VisualizationShowcase,
121
+ },
122
+ 'agent-console': {
123
+ title: 'AI Agent Console',
124
+ description:
125
+ 'AI agent orchestration with tools, agents, runs, and execution logs.',
126
+ component: AgentDashboard,
127
+ },
128
+ 'ai-chat-assistant': {
129
+ title: 'AI Chat Assistant',
130
+ description:
131
+ 'Focused assistant surface with reasoning, sources, suggestions, and MCP-aware tools.',
132
+ component: AiChatAssistantDashboard,
133
+ },
134
+ 'workflow-system': {
135
+ title: 'Workflow System',
136
+ description: 'Multi-step workflows with role-based approvals.',
137
+ component: WorkflowDashboard,
138
+ },
139
+ marketplace: {
140
+ title: 'Marketplace',
141
+ description:
142
+ 'Two-sided marketplace with stores, products, orders, and payouts.',
143
+ component: MarketplaceDashboard,
144
+ },
145
+ 'integration-hub': {
146
+ title: 'Integration Hub',
147
+ description:
148
+ 'Third-party integrations with connections, sync configs, and field mapping.',
149
+ component: IntegrationDashboard,
150
+ },
151
+ 'analytics-dashboard': {
152
+ title: 'Analytics Dashboard',
153
+ description: 'Custom dashboards with widgets and queries.',
154
+ component: AnalyticsDashboard,
155
+ },
156
+ };
157
+
158
+ export interface TemplatePreviewContentProps {
159
+ templateId: TemplateId;
160
+ }
161
+
162
+ export function TemplatePreviewContent({
163
+ templateId,
164
+ }: TemplatePreviewContentProps) {
165
+ const preview = PREVIEW_DEFINITIONS[templateId];
166
+
167
+ if (!preview) {
168
+ return null;
169
+ }
170
+
171
+ const PreviewComponent = preview.component;
172
+
173
+ return (
174
+ <TemplateShell
175
+ title={preview.title}
176
+ description={preview.description}
177
+ showSaveAction={false}
178
+ >
179
+ <PreviewComponent />
180
+ </TemplateShell>
181
+ );
182
+ }
@@ -0,0 +1,144 @@
1
+ 'use client';
2
+
3
+ import { cn } from '@contractspec/lib.ui-kit-core/utils';
4
+ import { Search } from 'lucide-react';
5
+ import type { TemplateSource } from './template-source';
6
+ import type { TemplateTagFacet } from './template-tag-visibility';
7
+
8
+ export interface TemplatesBrowseControlsProps {
9
+ registryConfigured: boolean;
10
+ availableSources: readonly TemplateSource[];
11
+ source: TemplateSource;
12
+ onSourceChange: (source: TemplateSource) => void;
13
+ search: string;
14
+ onSearchChange: (value: string) => void;
15
+ selectedTag: string | null;
16
+ onTagChange: (tag: string | null) => void;
17
+ showTagFilters: boolean;
18
+ visibleTagFacets: readonly TemplateTagFacet[];
19
+ hiddenTagFacets: readonly TemplateTagFacet[];
20
+ showAllTags: boolean;
21
+ onShowAllTagsChange: (expanded: boolean) => void;
22
+ }
23
+
24
+ export function TemplatesBrowseControls({
25
+ registryConfigured,
26
+ availableSources,
27
+ source,
28
+ onSourceChange,
29
+ search,
30
+ onSearchChange,
31
+ selectedTag,
32
+ onTagChange,
33
+ showTagFilters,
34
+ visibleTagFacets,
35
+ hiddenTagFacets,
36
+ showAllTags,
37
+ onShowAllTagsChange,
38
+ }: TemplatesBrowseControlsProps) {
39
+ return (
40
+ <section className="editorial-section">
41
+ <div className="editorial-shell space-y-6">
42
+ <div className="flex flex-col gap-6 lg:flex-row lg:items-end lg:justify-between">
43
+ <div className="max-w-3xl space-y-3">
44
+ <p className="editorial-kicker">Browse by source</p>
45
+ <h2 className="font-serif text-4xl tracking-[-0.04em]">
46
+ Use local scenarios for core proof, then scan the community.
47
+ </h2>
48
+ <p className="text-muted-foreground text-sm leading-7">
49
+ {registryConfigured
50
+ ? 'Local templates show the official adoption path. Community templates show where the ecosystem is pushing the system next.'
51
+ : 'Local templates show the official adoption path. Community browsing appears automatically when a registry URL is configured.'}
52
+ </p>
53
+ </div>
54
+ {registryConfigured ? (
55
+ <div className="flex gap-2">
56
+ {availableSources.map((option) => (
57
+ <button
58
+ key={option}
59
+ onClick={() => onSourceChange(option)}
60
+ className={cn(
61
+ 'rounded-full px-4 py-2 font-medium text-sm transition-colors',
62
+ {
63
+ 'bg-primary text-primary-foreground': source === option,
64
+ 'border border-border bg-card hover:bg-card/80':
65
+ source !== option,
66
+ }
67
+ )}
68
+ aria-pressed={source === option}
69
+ >
70
+ {option === 'local' ? 'Local' : 'Community'}
71
+ </button>
72
+ ))}
73
+ </div>
74
+ ) : null}
75
+ </div>
76
+
77
+ <div className="editorial-panel space-y-5">
78
+ <div className="relative">
79
+ <Search
80
+ className="absolute top-3.5 left-4 text-muted-foreground"
81
+ size={18}
82
+ />
83
+ <input
84
+ type="text"
85
+ placeholder="Search scenarios, industries, or tags"
86
+ value={search}
87
+ onChange={(event) => onSearchChange(event.target.value)}
88
+ 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"
89
+ aria-label="Search templates"
90
+ />
91
+ </div>
92
+ {showTagFilters ? (
93
+ <div className="space-y-3">
94
+ <div className="flex flex-wrap gap-2">
95
+ <button
96
+ onClick={() => onTagChange(null)}
97
+ className={cn(
98
+ 'rounded-full px-4 py-2 font-medium text-sm transition-colors',
99
+ {
100
+ 'bg-primary text-primary-foreground':
101
+ selectedTag === null,
102
+ 'border border-border bg-card hover:bg-card/80':
103
+ selectedTag !== null,
104
+ }
105
+ )}
106
+ aria-pressed={selectedTag === null}
107
+ >
108
+ All
109
+ </button>
110
+ {visibleTagFacets.map((facet) => (
111
+ <button
112
+ key={facet.tag}
113
+ onClick={() => onTagChange(facet.tag)}
114
+ className={cn(
115
+ 'rounded-full px-4 py-2 font-medium text-sm transition-colors',
116
+ {
117
+ 'bg-primary text-primary-foreground':
118
+ selectedTag === facet.tag,
119
+ 'border border-border bg-card hover:bg-card/80':
120
+ selectedTag !== facet.tag,
121
+ }
122
+ )}
123
+ aria-pressed={selectedTag === facet.tag}
124
+ >
125
+ {facet.tag}
126
+ </button>
127
+ ))}
128
+ </div>
129
+ {hiddenTagFacets.length > 0 || showAllTags ? (
130
+ <button
131
+ type="button"
132
+ onClick={() => onShowAllTagsChange(!showAllTags)}
133
+ className="text-muted-foreground text-sm transition-colors hover:text-foreground"
134
+ >
135
+ {showAllTags ? 'Show fewer' : 'More tags'}
136
+ </button>
137
+ ) : null}
138
+ </div>
139
+ ) : null}
140
+ </div>
141
+ </div>
142
+ </section>
143
+ );
144
+ }
@@ -0,0 +1,191 @@
1
+ 'use client';
2
+
3
+ import type { RegistryTemplate } from '@contractspec/lib.example-shared-ui';
4
+ import Link from 'next/link';
5
+ import { TemplateCard } from './TemplateCard';
6
+ import {
7
+ formatExampleKindLabel,
8
+ formatStabilityLabel,
9
+ type LocalTemplateCatalogItem,
10
+ } from './template-catalog';
11
+ import {
12
+ getLocalTemplatePreviewAction,
13
+ getRegistryTemplatePreviewAction,
14
+ } from './template-preview';
15
+ import type { TemplateSource } from './template-source';
16
+
17
+ export interface TemplatesCatalogSectionProps {
18
+ source: TemplateSource;
19
+ registryConfigured: boolean;
20
+ registryLoading: boolean;
21
+ registryHasTemplates: boolean;
22
+ localTemplates: readonly LocalTemplateCatalogItem[];
23
+ registryTemplates: readonly RegistryTemplate[];
24
+ localTemplateById: ReadonlyMap<string, LocalTemplateCatalogItem>;
25
+ onPreview: (templateId: string) => void;
26
+ onUseTemplate: (templateId: string, source: TemplateSource) => void;
27
+ hasSearch: boolean;
28
+ selectedTag: string | null;
29
+ }
30
+
31
+ export function TemplatesCatalogSection({
32
+ source,
33
+ registryConfigured,
34
+ registryLoading,
35
+ registryHasTemplates,
36
+ localTemplates,
37
+ registryTemplates,
38
+ localTemplateById,
39
+ onPreview,
40
+ onUseTemplate,
41
+ hasSearch,
42
+ selectedTag,
43
+ }: TemplatesCatalogSectionProps) {
44
+ const showRegistry = source === 'registry' && registryConfigured;
45
+ const emptyStateMessage = getEmptyStateMessage(hasSearch, selectedTag);
46
+
47
+ return (
48
+ <section className="section-padding">
49
+ <div className="editorial-shell">
50
+ {showRegistry ? (
51
+ registryLoading ? (
52
+ <div className="py-12 text-center">
53
+ <p className="text-muted-foreground">
54
+ Loading community templates…
55
+ </p>
56
+ </div>
57
+ ) : !registryHasTemplates ? (
58
+ <div className="py-12 text-center">
59
+ <p className="text-muted-foreground">
60
+ No community templates found.
61
+ </p>
62
+ </div>
63
+ ) : registryTemplates.length === 0 ? (
64
+ <div className="py-12 text-center">
65
+ <p className="text-muted-foreground">{emptyStateMessage}</p>
66
+ </div>
67
+ ) : (
68
+ <div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
69
+ {registryTemplates.map((template) => {
70
+ const localTemplate = localTemplateById.get(template.id);
71
+ const previewAction = getRegistryTemplatePreviewAction(
72
+ template,
73
+ localTemplate
74
+ );
75
+
76
+ return (
77
+ <TemplateCard
78
+ key={template.id}
79
+ title={template.name}
80
+ description={template.description}
81
+ metaBadges={['Community']}
82
+ tags={template.tags}
83
+ previewAction={
84
+ previewAction.kind === 'modal' ? (
85
+ <button
86
+ className="btn-ghost flex-1 text-center text-xs"
87
+ onClick={() => onPreview(template.id)}
88
+ >
89
+ Preview
90
+ </button>
91
+ ) : previewAction.kind === 'sandbox' ? (
92
+ <Link
93
+ href={previewAction.href}
94
+ className="btn-ghost flex-1 text-center text-xs"
95
+ >
96
+ Open Sandbox
97
+ </Link>
98
+ ) : (
99
+ <button
100
+ className="btn-ghost flex-1 cursor-not-allowed text-center text-xs opacity-60"
101
+ type="button"
102
+ disabled
103
+ >
104
+ Preview Unavailable
105
+ </button>
106
+ )
107
+ }
108
+ useAction={
109
+ <button
110
+ className="btn-primary flex-1 text-center text-xs"
111
+ onClick={() => onUseTemplate(template.id, 'registry')}
112
+ >
113
+ Use Template
114
+ </button>
115
+ }
116
+ />
117
+ );
118
+ })}
119
+ </div>
120
+ )
121
+ ) : localTemplates.length === 0 ? (
122
+ <div className="py-12 text-center">
123
+ <p className="text-muted-foreground">{emptyStateMessage}</p>
124
+ </div>
125
+ ) : (
126
+ <div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
127
+ {localTemplates.map((template) => {
128
+ const previewAction = getLocalTemplatePreviewAction(template);
129
+
130
+ return (
131
+ <TemplateCard
132
+ key={template.id}
133
+ title={template.title}
134
+ description={template.description}
135
+ isNew={template.isNew}
136
+ metaBadges={[
137
+ formatExampleKindLabel(template.kind),
138
+ formatStabilityLabel(template.stability),
139
+ ]}
140
+ tags={template.tags}
141
+ featureList={template.featureList}
142
+ previewAction={
143
+ previewAction.kind === 'modal' ? (
144
+ <button
145
+ className="btn-ghost flex-1 text-center text-xs"
146
+ onClick={() => onPreview(template.id)}
147
+ >
148
+ Preview
149
+ </button>
150
+ ) : (
151
+ <Link
152
+ href={previewAction.href}
153
+ className="btn-ghost flex-1 text-center text-xs"
154
+ >
155
+ Open Sandbox
156
+ </Link>
157
+ )
158
+ }
159
+ useAction={
160
+ <button
161
+ className="btn-primary flex-1 text-center text-xs"
162
+ onClick={() => onUseTemplate(template.id, 'local')}
163
+ >
164
+ Use Template
165
+ </button>
166
+ }
167
+ />
168
+ );
169
+ })}
170
+ </div>
171
+ )}
172
+ </div>
173
+ </section>
174
+ );
175
+ }
176
+
177
+ function getEmptyStateMessage(
178
+ hasSearch: boolean,
179
+ selectedTag: string | null
180
+ ): string {
181
+ if (selectedTag !== null && hasSearch) {
182
+ return 'No templates match this tag for the current search.';
183
+ }
184
+ if (selectedTag !== null) {
185
+ return 'No templates match this tag. Try another tag or reset filters.';
186
+ }
187
+ if (hasSearch) {
188
+ return 'No templates match your search. Try a different keyword.';
189
+ }
190
+ return 'No templates match your filters. Try a different search.';
191
+ }