@contractspec/lib.example-shared-ui 6.0.6 → 6.0.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 (82) hide show
  1. package/.turbo/turbo-build.log +90 -84
  2. package/AGENTS.md +43 -25
  3. package/CHANGELOG.md +24 -0
  4. package/README.md +63 -35
  5. package/dist/EvolutionDashboard.js +9 -9
  6. package/dist/EvolutionSidebar.js +15 -15
  7. package/dist/LocalDataIndicator.js +3 -3
  8. package/dist/MarkdownView.d.ts +0 -7
  9. package/dist/MarkdownView.js +76 -172
  10. package/dist/PersonalizationInsights.js +12 -12
  11. package/dist/SaveToStudioButton.js +2 -2
  12. package/dist/SpecDrivenTemplateShell.d.ts +1 -1
  13. package/dist/SpecDrivenTemplateShell.js +10 -10
  14. package/dist/SpecEditorPanel.js +3 -3
  15. package/dist/TemplateShell.js +10 -10
  16. package/dist/browser/EvolutionDashboard.js +9 -9
  17. package/dist/browser/EvolutionSidebar.js +15 -15
  18. package/dist/browser/LocalDataIndicator.js +3 -3
  19. package/dist/browser/MarkdownView.js +76 -172
  20. package/dist/browser/PersonalizationInsights.js +12 -12
  21. package/dist/browser/SaveToStudioButton.js +2 -2
  22. package/dist/browser/SpecDrivenTemplateShell.js +10 -10
  23. package/dist/browser/SpecEditorPanel.js +3 -3
  24. package/dist/browser/TemplateShell.js +10 -10
  25. package/dist/browser/hooks/index.js +29 -29
  26. package/dist/browser/index.js +193 -286
  27. package/dist/browser/lib/component-registry.js +1 -1
  28. package/dist/browser/markdown/formatPresentationName.js +9 -0
  29. package/dist/browser/markdown/useMarkdownPresentation.js +65 -0
  30. package/dist/hooks/index.d.ts +3 -3
  31. package/dist/hooks/index.js +29 -29
  32. package/dist/index.d.ts +12 -11
  33. package/dist/index.js +193 -286
  34. package/dist/lib/component-registry.js +1 -1
  35. package/dist/markdown/formatPresentationName.d.ts +1 -0
  36. package/dist/markdown/formatPresentationName.js +10 -0
  37. package/dist/markdown/useMarkdownPresentation.d.ts +21 -0
  38. package/dist/markdown/useMarkdownPresentation.js +66 -0
  39. package/dist/node/EvolutionDashboard.js +9 -9
  40. package/dist/node/EvolutionSidebar.js +15 -15
  41. package/dist/node/LocalDataIndicator.js +3 -3
  42. package/dist/node/MarkdownView.js +76 -172
  43. package/dist/node/PersonalizationInsights.js +12 -12
  44. package/dist/node/SaveToStudioButton.js +2 -2
  45. package/dist/node/SpecDrivenTemplateShell.js +10 -10
  46. package/dist/node/SpecEditorPanel.js +3 -3
  47. package/dist/node/TemplateShell.js +10 -10
  48. package/dist/node/hooks/index.js +29 -29
  49. package/dist/node/index.js +193 -286
  50. package/dist/node/lib/component-registry.js +1 -1
  51. package/dist/node/markdown/formatPresentationName.js +9 -0
  52. package/dist/node/markdown/useMarkdownPresentation.js +65 -0
  53. package/dist/utils/index.d.ts +1 -1
  54. package/package.json +40 -13
  55. package/src/EvolutionDashboard.tsx +415 -415
  56. package/src/EvolutionSidebar.tsx +245 -245
  57. package/src/LocalDataIndicator.tsx +28 -28
  58. package/src/MarkdownView.tsx +119 -372
  59. package/src/OverlayContextProvider.tsx +272 -272
  60. package/src/PersonalizationInsights.tsx +232 -232
  61. package/src/SaveToStudioButton.tsx +51 -51
  62. package/src/SpecDrivenTemplateShell.tsx +59 -59
  63. package/src/SpecEditorPanel.tsx +138 -138
  64. package/src/TemplateShell.tsx +50 -50
  65. package/src/bundles/ExampleTemplateBundle.ts +78 -78
  66. package/src/hooks/index.ts +3 -3
  67. package/src/hooks/useBehaviorTracking.ts +252 -252
  68. package/src/hooks/useEvolution.ts +437 -437
  69. package/src/hooks/useRegistryTemplates.ts +42 -42
  70. package/src/hooks/useSpecContent.ts +214 -214
  71. package/src/hooks/useWorkflowComposer.ts +567 -567
  72. package/src/index.ts +12 -11
  73. package/src/lib/component-registry.tsx +40 -40
  74. package/src/lib/runtime-context.tsx +31 -31
  75. package/src/lib/types.ts +57 -57
  76. package/src/markdown/formatPresentationName.ts +9 -0
  77. package/src/markdown/useMarkdownPresentation.ts +107 -0
  78. package/src/overlay-types.ts +15 -15
  79. package/src/utils/fetchPresentationData.ts +13 -13
  80. package/src/utils/generateSpecFromTemplate.ts +29 -29
  81. package/src/utils/index.ts +1 -1
  82. package/tsconfig.json +8 -8
@@ -1,21 +1,21 @@
1
1
  'use client';
2
2
 
3
- import { useCallback, useMemo, useState } from 'react';
4
3
  import { Button } from '@contractspec/lib.design-system';
5
- import { Card } from '@contractspec/lib.ui-kit-web/ui/card';
6
4
  import { Badge } from '@contractspec/lib.ui-kit-web/ui/badge';
7
- import type { TemplateId } from './lib/types';
5
+ import { Card } from '@contractspec/lib.ui-kit-web/ui/card';
6
+ import { useCallback, useMemo, useState } from 'react';
8
7
  import {
9
- type BehaviorSummary,
10
- useBehaviorTracking,
8
+ type BehaviorSummary,
9
+ useBehaviorTracking,
11
10
  } from './hooks/useBehaviorTracking';
11
+ import type { TemplateId } from './lib/types';
12
12
 
13
13
  export interface PersonalizationInsightsProps {
14
- templateId: TemplateId;
15
- /** Whether to show in collapsed mode */
16
- collapsed?: boolean;
17
- /** Toggle collapsed state */
18
- onToggle?: () => void;
14
+ templateId: TemplateId;
15
+ /** Whether to show in collapsed mode */
16
+ collapsed?: boolean;
17
+ /** Toggle collapsed state */
18
+ onToggle?: () => void;
19
19
  }
20
20
 
21
21
  /**
@@ -23,271 +23,271 @@ export interface PersonalizationInsightsProps {
23
23
  * Displays usage patterns, recommendations, and feature adoption.
24
24
  */
25
25
  export function PersonalizationInsights({
26
- templateId,
27
- collapsed = false,
28
- onToggle,
26
+ templateId,
27
+ collapsed = false,
28
+ onToggle,
29
29
  }: PersonalizationInsightsProps) {
30
- const { getSummary, eventCount, clear, sessionStart } =
31
- useBehaviorTracking(templateId);
30
+ const { getSummary, eventCount, clear, sessionStart } =
31
+ useBehaviorTracking(templateId);
32
32
 
33
- const [showDetails, setShowDetails] = useState(false);
33
+ const [showDetails, setShowDetails] = useState(false);
34
34
 
35
- const summary = useMemo(() => getSummary(), [getSummary]);
35
+ const summary = useMemo(() => getSummary(), [getSummary]);
36
36
 
37
- const formatDuration = useCallback((ms: number) => {
38
- const seconds = Math.floor(ms / 1000);
39
- const minutes = Math.floor(seconds / 60);
40
- const hours = Math.floor(minutes / 60);
37
+ const formatDuration = useCallback((ms: number) => {
38
+ const seconds = Math.floor(ms / 1000);
39
+ const minutes = Math.floor(seconds / 60);
40
+ const hours = Math.floor(minutes / 60);
41
41
 
42
- if (hours > 0) {
43
- return `${hours}h ${minutes % 60}m`;
44
- }
45
- if (minutes > 0) {
46
- return `${minutes}m ${seconds % 60}s`;
47
- }
48
- return `${seconds}s`;
49
- }, []);
42
+ if (hours > 0) {
43
+ return `${hours}h ${minutes % 60}m`;
44
+ }
45
+ if (minutes > 0) {
46
+ return `${minutes}m ${seconds % 60}s`;
47
+ }
48
+ return `${seconds}s`;
49
+ }, []);
50
50
 
51
- const handleClear = useCallback(() => {
52
- clear();
53
- }, [clear]);
51
+ const handleClear = useCallback(() => {
52
+ clear();
53
+ }, [clear]);
54
54
 
55
- // Collapsed view
56
- if (collapsed) {
57
- return (
58
- <button
59
- onClick={onToggle}
60
- className="flex items-center gap-2 rounded-lg border border-blue-500/30 bg-blue-500/10 px-3 py-2 text-sm transition hover:bg-blue-500/20"
61
- type="button"
62
- >
63
- <span>📊</span>
64
- <span>Insights</span>
65
- <Badge variant="secondary">{eventCount}</Badge>
66
- </button>
67
- );
68
- }
55
+ // Collapsed view
56
+ if (collapsed) {
57
+ return (
58
+ <button
59
+ onClick={onToggle}
60
+ className="flex items-center gap-2 rounded-lg border border-blue-500/30 bg-blue-500/10 px-3 py-2 text-sm transition hover:bg-blue-500/20"
61
+ type="button"
62
+ >
63
+ <span>📊</span>
64
+ <span>Insights</span>
65
+ <Badge variant="secondary">{eventCount}</Badge>
66
+ </button>
67
+ );
68
+ }
69
69
 
70
- return (
71
- <Card className="overflow-hidden">
72
- {/* Header */}
73
- <div className="flex items-center justify-between border-b border-blue-500/20 bg-blue-500/5 px-4 py-3">
74
- <div className="flex items-center gap-2">
75
- <span>📊</span>
76
- <span className="font-semibold">Personalization Insights</span>
77
- </div>
78
- <div className="flex items-center gap-2">
79
- <Button
80
- variant="ghost"
81
- size="sm"
82
- onPress={() => setShowDetails(!showDetails)}
83
- >
84
- {showDetails ? 'Hide Details' : 'Show Details'}
85
- </Button>
86
- {onToggle && (
87
- <button
88
- onClick={onToggle}
89
- className="text-muted-foreground hover:text-foreground p-1"
90
- type="button"
91
- title="Collapse"
92
- >
93
-
94
- </button>
95
- )}
96
- </div>
97
- </div>
70
+ return (
71
+ <Card className="overflow-hidden">
72
+ {/* Header */}
73
+ <div className="flex items-center justify-between border-blue-500/20 border-b bg-blue-500/5 px-4 py-3">
74
+ <div className="flex items-center gap-2">
75
+ <span>📊</span>
76
+ <span className="font-semibold">Personalization Insights</span>
77
+ </div>
78
+ <div className="flex items-center gap-2">
79
+ <Button
80
+ variant="ghost"
81
+ size="sm"
82
+ onPress={() => setShowDetails(!showDetails)}
83
+ >
84
+ {showDetails ? 'Hide Details' : 'Show Details'}
85
+ </Button>
86
+ {onToggle && (
87
+ <button
88
+ onClick={onToggle}
89
+ className="p-1 text-muted-foreground hover:text-foreground"
90
+ type="button"
91
+ title="Collapse"
92
+ >
93
+
94
+ </button>
95
+ )}
96
+ </div>
97
+ </div>
98
98
 
99
- <div className="p-4">
100
- {/* Quick Stats */}
101
- <div className="mb-4 grid grid-cols-2 gap-3 md:grid-cols-4">
102
- <StatCard
103
- label="Session Time"
104
- value={formatDuration(summary.sessionDuration)}
105
- icon="⏱️"
106
- />
107
- <StatCard
108
- label="Events Tracked"
109
- value={summary.totalEvents.toString()}
110
- icon="📈"
111
- />
112
- <StatCard
113
- label="Features Used"
114
- value={`${summary.featuresUsed.length}/${summary.featuresUsed.length + summary.unusedFeatures.length}`}
115
- icon="✨"
116
- />
117
- <StatCard
118
- label="Errors"
119
- value={summary.errorCount.toString()}
120
- icon="⚠️"
121
- variant={summary.errorCount > 0 ? 'warning' : 'success'}
122
- />
123
- </div>
99
+ <div className="p-4">
100
+ {/* Quick Stats */}
101
+ <div className="mb-4 grid grid-cols-2 gap-3 md:grid-cols-4">
102
+ <StatCard
103
+ label="Session Time"
104
+ value={formatDuration(summary.sessionDuration)}
105
+ icon="⏱️"
106
+ />
107
+ <StatCard
108
+ label="Events Tracked"
109
+ value={summary.totalEvents.toString()}
110
+ icon="📈"
111
+ />
112
+ <StatCard
113
+ label="Features Used"
114
+ value={`${summary.featuresUsed.length}/${summary.featuresUsed.length + summary.unusedFeatures.length}`}
115
+ icon="✨"
116
+ />
117
+ <StatCard
118
+ label="Errors"
119
+ value={summary.errorCount.toString()}
120
+ icon="⚠️"
121
+ variant={summary.errorCount > 0 ? 'warning' : 'success'}
122
+ />
123
+ </div>
124
124
 
125
- {/* Recommendations */}
126
- {summary.recommendations.length > 0 && (
127
- <div className="mb-4">
128
- <h4 className="mb-2 text-xs font-semibold text-blue-400 uppercase">
129
- Recommendations
130
- </h4>
131
- <ul className="space-y-1">
132
- {summary.recommendations.map((rec, index) => (
133
- <li key={index} className="flex items-start gap-2 text-sm">
134
- <span className="text-blue-400">💡</span>
135
- <span>{rec}</span>
136
- </li>
137
- ))}
138
- </ul>
139
- </div>
140
- )}
125
+ {/* Recommendations */}
126
+ {summary.recommendations.length > 0 && (
127
+ <div className="mb-4">
128
+ <h4 className="mb-2 font-semibold text-blue-400 text-xs uppercase">
129
+ Recommendations
130
+ </h4>
131
+ <ul className="space-y-1">
132
+ {summary.recommendations.map((rec, index) => (
133
+ <li key={index} className="flex items-start gap-2 text-sm">
134
+ <span className="text-blue-400">💡</span>
135
+ <span>{rec}</span>
136
+ </li>
137
+ ))}
138
+ </ul>
139
+ </div>
140
+ )}
141
141
 
142
- {/* Unused Features */}
143
- {summary.unusedFeatures.length > 0 && (
144
- <div className="mb-4">
145
- <h4 className="mb-2 text-xs font-semibold text-blue-400 uppercase">
146
- Try These Features
147
- </h4>
148
- <div className="flex flex-wrap gap-2">
149
- {summary.unusedFeatures.slice(0, 5).map((feature) => (
150
- <Badge key={feature} variant="secondary">
151
- {formatFeatureName(feature)}
152
- </Badge>
153
- ))}
154
- </div>
155
- </div>
156
- )}
142
+ {/* Unused Features */}
143
+ {summary.unusedFeatures.length > 0 && (
144
+ <div className="mb-4">
145
+ <h4 className="mb-2 font-semibold text-blue-400 text-xs uppercase">
146
+ Try These Features
147
+ </h4>
148
+ <div className="flex flex-wrap gap-2">
149
+ {summary.unusedFeatures.slice(0, 5).map((feature) => (
150
+ <Badge key={feature} variant="secondary">
151
+ {formatFeatureName(feature)}
152
+ </Badge>
153
+ ))}
154
+ </div>
155
+ </div>
156
+ )}
157
157
 
158
- {/* Detailed View */}
159
- {showDetails && (
160
- <DetailedInsights summary={summary} sessionStart={sessionStart} />
161
- )}
158
+ {/* Detailed View */}
159
+ {showDetails && (
160
+ <DetailedInsights summary={summary} sessionStart={sessionStart} />
161
+ )}
162
162
 
163
- {/* Footer Actions */}
164
- <div className="mt-4 flex justify-end border-t border-blue-500/10 pt-4">
165
- <Button variant="ghost" size="sm" onPress={handleClear}>
166
- Clear Data
167
- </Button>
168
- </div>
169
- </div>
170
- </Card>
171
- );
163
+ {/* Footer Actions */}
164
+ <div className="mt-4 flex justify-end border-blue-500/10 border-t pt-4">
165
+ <Button variant="ghost" size="sm" onPress={handleClear}>
166
+ Clear Data
167
+ </Button>
168
+ </div>
169
+ </div>
170
+ </Card>
171
+ );
172
172
  }
173
173
 
174
174
  /**
175
175
  * Stat card component
176
176
  */
177
177
  function StatCard({
178
- label,
179
- value,
180
- icon,
181
- variant = 'default',
178
+ label,
179
+ value,
180
+ icon,
181
+ variant = 'default',
182
182
  }: {
183
- label: string;
184
- value: string;
185
- icon: string;
186
- variant?: 'default' | 'warning' | 'success';
183
+ label: string;
184
+ value: string;
185
+ icon: string;
186
+ variant?: 'default' | 'warning' | 'success';
187
187
  }) {
188
- const bgColors = {
189
- default: 'bg-blue-500/5 border-blue-500/20',
190
- warning: 'bg-amber-500/5 border-amber-500/20',
191
- success: 'bg-green-500/5 border-green-500/20',
192
- };
188
+ const bgColors = {
189
+ default: 'bg-blue-500/5 border-blue-500/20',
190
+ warning: 'bg-amber-500/5 border-amber-500/20',
191
+ success: 'bg-green-500/5 border-green-500/20',
192
+ };
193
193
 
194
- return (
195
- <div className={`rounded-lg border p-3 text-center ${bgColors[variant]}`}>
196
- <div className="mb-1 text-lg">{icon}</div>
197
- <div className="text-lg font-bold">{value}</div>
198
- <div className="text-muted-foreground text-xs">{label}</div>
199
- </div>
200
- );
194
+ return (
195
+ <div className={`rounded-lg border p-3 text-center ${bgColors[variant]}`}>
196
+ <div className="mb-1 text-lg">{icon}</div>
197
+ <div className="font-bold text-lg">{value}</div>
198
+ <div className="text-muted-foreground text-xs">{label}</div>
199
+ </div>
200
+ );
201
201
  }
202
202
 
203
203
  /**
204
204
  * Detailed insights section
205
205
  */
206
206
  function DetailedInsights({
207
- summary,
208
- sessionStart,
207
+ summary,
208
+ sessionStart,
209
209
  }: {
210
- summary: BehaviorSummary;
211
- sessionStart: Date;
210
+ summary: BehaviorSummary;
211
+ sessionStart: Date;
212
212
  }) {
213
- return (
214
- <div className="mt-4 space-y-4 border-t border-blue-500/10 pt-4">
215
- {/* Most Used Templates */}
216
- {summary.mostUsedTemplates.length > 0 && (
217
- <div>
218
- <h4 className="mb-2 text-xs font-semibold text-blue-400 uppercase">
219
- Most Used Templates
220
- </h4>
221
- <div className="space-y-1">
222
- {summary.mostUsedTemplates.map(({ templateId, count }) => (
223
- <div
224
- key={templateId}
225
- className="flex items-center justify-between text-sm"
226
- >
227
- <span>{formatTemplateId(templateId)}</span>
228
- <span className="text-muted-foreground">{count} events</span>
229
- </div>
230
- ))}
231
- </div>
232
- </div>
233
- )}
213
+ return (
214
+ <div className="mt-4 space-y-4 border-blue-500/10 border-t pt-4">
215
+ {/* Most Used Templates */}
216
+ {summary.mostUsedTemplates.length > 0 && (
217
+ <div>
218
+ <h4 className="mb-2 font-semibold text-blue-400 text-xs uppercase">
219
+ Most Used Templates
220
+ </h4>
221
+ <div className="space-y-1">
222
+ {summary.mostUsedTemplates.map(({ templateId, count }) => (
223
+ <div
224
+ key={templateId}
225
+ className="flex items-center justify-between text-sm"
226
+ >
227
+ <span>{formatTemplateId(templateId)}</span>
228
+ <span className="text-muted-foreground">{count} events</span>
229
+ </div>
230
+ ))}
231
+ </div>
232
+ </div>
233
+ )}
234
234
 
235
- {/* Most Used Modes */}
236
- {summary.mostUsedModes.length > 0 && (
237
- <div>
238
- <h4 className="mb-2 text-xs font-semibold text-blue-400 uppercase">
239
- Mode Usage
240
- </h4>
241
- <div className="space-y-1">
242
- {summary.mostUsedModes.map(({ mode, count }) => (
243
- <div
244
- key={mode}
245
- className="flex items-center justify-between text-sm"
246
- >
247
- <span>{formatFeatureName(mode)}</span>
248
- <span className="text-muted-foreground">{count} switches</span>
249
- </div>
250
- ))}
251
- </div>
252
- </div>
253
- )}
235
+ {/* Most Used Modes */}
236
+ {summary.mostUsedModes.length > 0 && (
237
+ <div>
238
+ <h4 className="mb-2 font-semibold text-blue-400 text-xs uppercase">
239
+ Mode Usage
240
+ </h4>
241
+ <div className="space-y-1">
242
+ {summary.mostUsedModes.map(({ mode, count }) => (
243
+ <div
244
+ key={mode}
245
+ className="flex items-center justify-between text-sm"
246
+ >
247
+ <span>{formatFeatureName(mode)}</span>
248
+ <span className="text-muted-foreground">{count} switches</span>
249
+ </div>
250
+ ))}
251
+ </div>
252
+ </div>
253
+ )}
254
254
 
255
- {/* Features Used */}
256
- <div>
257
- <h4 className="mb-2 text-xs font-semibold text-blue-400 uppercase">
258
- Features Used
259
- </h4>
260
- <div className="flex flex-wrap gap-2">
261
- {summary.featuresUsed.map((feature) => (
262
- <Badge
263
- key={feature}
264
- variant="default"
265
- className="border-green-500/30 bg-green-500/20 text-green-400"
266
- >
267
- ✓ {formatFeatureName(feature)}
268
- </Badge>
269
- ))}
270
- </div>
271
- </div>
255
+ {/* Features Used */}
256
+ <div>
257
+ <h4 className="mb-2 font-semibold text-blue-400 text-xs uppercase">
258
+ Features Used
259
+ </h4>
260
+ <div className="flex flex-wrap gap-2">
261
+ {summary.featuresUsed.map((feature) => (
262
+ <Badge
263
+ key={feature}
264
+ variant="default"
265
+ className="border-green-500/30 bg-green-500/20 text-green-400"
266
+ >
267
+ ✓ {formatFeatureName(feature)}
268
+ </Badge>
269
+ ))}
270
+ </div>
271
+ </div>
272
272
 
273
- {/* Session Info */}
274
- <div className="text-muted-foreground text-xs">
275
- Session started: {sessionStart.toLocaleString()}
276
- </div>
277
- </div>
278
- );
273
+ {/* Session Info */}
274
+ <div className="text-muted-foreground text-xs">
275
+ Session started: {sessionStart.toLocaleString()}
276
+ </div>
277
+ </div>
278
+ );
279
279
  }
280
280
 
281
281
  /**
282
282
  * Format feature name for display
283
283
  */
284
284
  function formatFeatureName(feature: string): string {
285
- return feature.replace(/_/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
285
+ return feature.replace(/_/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
286
286
  }
287
287
 
288
288
  /**
289
289
  * Format template ID for display
290
290
  */
291
291
  function formatTemplateId(id: string): string {
292
- return id.replace(/-/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
292
+ return id.replace(/-/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
293
293
  }
@@ -1,64 +1,64 @@
1
1
  'use client';
2
2
 
3
- import { useState } from 'react';
4
3
  import { Sparkles } from 'lucide-react';
4
+ import { useState } from 'react';
5
5
  import { useTemplateRuntime } from './lib/runtime-context';
6
6
 
7
7
  export interface SaveToStudioButtonProps {
8
- organizationId?: string;
9
- projectName?: string;
10
- endpoint?: string;
11
- token?: string;
8
+ organizationId?: string;
9
+ projectName?: string;
10
+ endpoint?: string;
11
+ token?: string;
12
12
  }
13
13
 
14
14
  export function SaveToStudioButton({
15
- organizationId = 'demo-org',
16
- projectName,
17
- endpoint,
18
- token,
15
+ organizationId = 'demo-org',
16
+ projectName,
17
+ endpoint,
18
+ token,
19
19
  }: SaveToStudioButtonProps) {
20
- const { installer, templateId, template } = useTemplateRuntime();
21
- const [status, setStatus] = useState<'idle' | 'saving' | 'saved' | 'error'>(
22
- 'idle'
23
- );
24
- const [error, setError] = useState<string | null>(null);
20
+ const { installer, templateId, template } = useTemplateRuntime();
21
+ const [status, setStatus] = useState<'idle' | 'saving' | 'saved' | 'error'>(
22
+ 'idle'
23
+ );
24
+ const [error, setError] = useState<string | null>(null);
25
25
 
26
- const handleSave = async () => {
27
- setStatus('saving');
28
- setError(null);
29
- try {
30
- await installer.saveToStudio({
31
- templateId,
32
- projectName: projectName ?? `${template.name} demo`,
33
- organizationId,
34
- endpoint,
35
- token,
36
- });
37
- setStatus('saved');
38
- setTimeout(() => setStatus('idle'), 3000);
39
- } catch (err) {
40
- setStatus('error');
41
- setError(err instanceof Error ? err.message : 'Unknown error');
42
- }
43
- };
26
+ const handleSave = async () => {
27
+ setStatus('saving');
28
+ setError(null);
29
+ try {
30
+ await installer.saveToStudio({
31
+ templateId,
32
+ projectName: projectName ?? `${template.name} demo`,
33
+ organizationId,
34
+ endpoint,
35
+ token,
36
+ });
37
+ setStatus('saved');
38
+ setTimeout(() => setStatus('idle'), 3000);
39
+ } catch (err) {
40
+ setStatus('error');
41
+ setError(err instanceof Error ? err.message : 'Unknown error');
42
+ }
43
+ };
44
44
 
45
- return (
46
- <div className="flex flex-col items-end gap-1">
47
- <button
48
- type="button"
49
- className="btn-primary inline-flex items-center gap-2 text-sm"
50
- onClick={handleSave}
51
- disabled={status === 'saving'}
52
- >
53
- <Sparkles className="h-4 w-4" />
54
- {status === 'saving' ? 'Publishing…' : 'Save to Studio'}
55
- </button>
56
- {status === 'error' && error ? (
57
- <p className="text-destructive text-xs">{error}</p>
58
- ) : null}
59
- {status === 'saved' ? (
60
- <p className="text-xs text-emerald-400">Template sent to Studio.</p>
61
- ) : null}
62
- </div>
63
- );
45
+ return (
46
+ <div className="flex flex-col items-end gap-1">
47
+ <button
48
+ type="button"
49
+ className="btn-primary inline-flex items-center gap-2 text-sm"
50
+ onClick={handleSave}
51
+ disabled={status === 'saving'}
52
+ >
53
+ <Sparkles className="h-4 w-4" />
54
+ {status === 'saving' ? 'Publishing…' : 'Save to Studio'}
55
+ </button>
56
+ {status === 'error' && error ? (
57
+ <p className="text-destructive text-xs">{error}</p>
58
+ ) : null}
59
+ {status === 'saved' ? (
60
+ <p className="text-emerald-400 text-xs">Template sent to Studio.</p>
61
+ ) : null}
62
+ </div>
63
+ );
64
64
  }