@kyro-cms/admin 0.10.14 → 0.10.19

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kyro-cms/admin",
3
- "version": "0.10.14",
3
+ "version": "0.10.19",
4
4
  "engines": {
5
5
  "node": ">=22"
6
6
  },
@@ -272,7 +272,9 @@ export function AutoForm({
272
272
 
273
273
  const result = await resp.json();
274
274
  if (result.data) {
275
- const restoredData = { ...formData, ...result.data };
275
+ // Omit historical timestamps so OCC doesn't panic on save
276
+ const { updatedAt, createdAt, ...historicalData } = result.data;
277
+ const restoredData = { ...formData, ...historicalData };
276
278
  setFormData(restoredData);
277
279
  useAutoFormStore.getState().loadDocument(restoredData, restoredData);
278
280
  onActionSuccess?.("Version restored successfully");
@@ -393,11 +393,19 @@ export function GraphQLPlayground({
393
393
  const [token, setToken] = useState<string>("");
394
394
  const [showToken, setShowToken] = useState(false);
395
395
  const [isConnected, setIsConnected] = useState(false);
396
- const [tab, setTab] = useState<QueryTab>({
397
- id: "default",
398
- query: initialQuery || DEFAULT_QUERY,
399
- variables: initialVariables || "{}",
400
- headers: "",
396
+ const [tab, setTab] = useState<QueryTab>(() => {
397
+ if (typeof window !== "undefined") {
398
+ try {
399
+ const saved = localStorage.getItem("kyro_graphql_tab");
400
+ if (saved) return JSON.parse(saved);
401
+ } catch {}
402
+ }
403
+ return {
404
+ id: "default",
405
+ query: initialQuery || DEFAULT_QUERY,
406
+ variables: initialVariables || "{}",
407
+ headers: "",
408
+ };
401
409
  });
402
410
  const [response, setResponse] = useState<string>("");
403
411
  const [isLoading, setIsLoading] = useState(false);
@@ -411,7 +419,15 @@ export function GraphQLPlayground({
411
419
  const [rightTab, setRightTab] = useState<"response" | "docs" | "history">(
412
420
  initialShowDocs ? "docs" : "response",
413
421
  );
414
- const [history, setHistory] = useState<HistoryEntry[]>([]);
422
+ const [history, setHistory] = useState<HistoryEntry[]>(() => {
423
+ if (typeof window !== "undefined") {
424
+ try {
425
+ const saved = localStorage.getItem("kyro_graphql_history");
426
+ if (saved) return JSON.parse(saved);
427
+ } catch {}
428
+ }
429
+ return [];
430
+ });
415
431
  const [lastDuration, setLastDuration] = useState<number>(0);
416
432
  const [lastStatus, setLastStatus] = useState<number>(0);
417
433
  const [copied, setCopied] = useState(false);
@@ -426,6 +442,18 @@ export function GraphQLPlayground({
426
442
  setIsMounted(true);
427
443
  }, []);
428
444
 
445
+ useEffect(() => {
446
+ if (isMounted) {
447
+ localStorage.setItem("kyro_graphql_tab", JSON.stringify(tab));
448
+ }
449
+ }, [tab, isMounted]);
450
+
451
+ useEffect(() => {
452
+ if (isMounted) {
453
+ localStorage.setItem("kyro_graphql_history", JSON.stringify(history));
454
+ }
455
+ }, [history, isMounted]);
456
+
429
457
  useEffect(() => {
430
458
  const check = () => setIsDesktop(window.innerWidth >= 768);
431
459
  check();
@@ -83,13 +83,21 @@ export function RestPlayground({ collections = [] }: RestPlaygroundProps) {
83
83
  const [history, setHistory] = useState<HistoryItem[]>([]);
84
84
  const [envVars, setEnvVars] = useState<EnvVariable[]>([]);
85
85
  const [selectedRequest, setSelectedRequest] = useState<SavedRequest | null>(null);
86
- const [currentRequest, setCurrentRequest] = useState<SavedRequest>({
87
- id: "new",
88
- name: "Untitled Request",
89
- method: "GET",
90
- url: "",
91
- headers: {},
92
- body: "",
86
+ const [currentRequest, setCurrentRequest] = useState<SavedRequest>(() => {
87
+ if (typeof window !== "undefined") {
88
+ try {
89
+ const saved = localStorage.getItem("kyro_rest_current");
90
+ if (saved) return JSON.parse(saved);
91
+ } catch {}
92
+ }
93
+ return {
94
+ id: "new",
95
+ name: "Untitled Request",
96
+ method: "GET",
97
+ url: "",
98
+ headers: {},
99
+ body: "",
100
+ };
93
101
  });
94
102
  const [response, setResponse] = useState<{ status: number; duration: number; size: number; data: any } | null>(null);
95
103
  const [loading, setLoading] = useState(false);
@@ -136,6 +144,10 @@ export function RestPlayground({ collections = [] }: RestPlaygroundProps) {
136
144
  if (isMounted) localStorage.setItem(STORAGE_KEYS.env, JSON.stringify(envVars));
137
145
  }, [envVars, isMounted]);
138
146
 
147
+ useEffect(() => {
148
+ if (isMounted) localStorage.setItem("kyro_rest_current", JSON.stringify(currentRequest));
149
+ }, [currentRequest, isMounted]);
150
+
139
151
  const resolveUrl = (url: string) => {
140
152
  let resolved = url;
141
153
  envVars.forEach((v) => {
@@ -33,7 +33,7 @@ export function AutoFormApiView({
33
33
  <label className="text-[10px] font-bold tracking-[0.1em] text-[var(--kyro-text-secondary)] opacity-50 block mb-2">
34
34
  Reference Path
35
35
  </label>
36
- <div className="bg-[var(--kyro-bg-secondary)] px-4 py-3 rounded-xl border border-[var(--kyro-border)] text-[11px] font-mono break-all selection:bg-[var(--kyro-primary)]/20 text-[var(--kyro-text-primary)]">
36
+ <div className="bg-[var(--kyro-bg-secondary)] px-4 py-3 rounded-md border border-[var(--kyro-border)] text-[11px] font-mono break-all selection:bg-[var(--kyro-primary)]/20 text-[var(--kyro-text-primary)]">
37
37
  {globalSlug
38
38
  ? `kyro.globals('${globalSlug}').get()`
39
39
  : formData.id
@@ -167,7 +167,7 @@ export function AutoFormVersionView({
167
167
  )}
168
168
  </div>
169
169
  <div className="text-[11px] text-[var(--kyro-text-muted)]">
170
- {new Date(v.createdAt as string).toLocaleString("en-US", {
170
+ {new Date((v.createdAt || (v as any).created_at) as string).toLocaleString("en-US", {
171
171
  month: "short",
172
172
  day: "numeric",
173
173
  hour: "2-digit",
@@ -236,7 +236,7 @@ export function AutoFormVersionView({
236
236
  </div>
237
237
  <div className="flex items-center gap-2 mt-1 flex-wrap">
238
238
  <span className="text-[11px] text-[var(--kyro-text-muted)]">
239
- {new Date(v.createdAt as string).toLocaleString("en-US", {
239
+ {new Date((v.createdAt || (v as any).created_at) as string).toLocaleString("en-US", {
240
240
  month: "short",
241
241
  day: "numeric",
242
242
  hour: "2-digit",
@@ -8,9 +8,7 @@ import { CodeBlock } from "../../blocks/CodeBlock";
8
8
  import { FileBlock } from "../../blocks/FileBlock";
9
9
  import { AccordionBlock } from "../../blocks/AccordionBlock";
10
10
  import { RichTextBlock } from "../../blocks/RichTextBlock";
11
-
12
11
  import { HeroBlock } from "../../blocks/HeroBlock";
13
- import { HeadingSubheadingBlock } from "../../blocks/HeadingSubheadingBlock";
14
12
  import { CardBlock } from "../../blocks/CardBlock";
15
13
  import { ArrayBlock } from "../../blocks/ArrayBlock";
16
14
  import { RelationshipBlock } from "../../blocks/RelationshipBlock";
@@ -53,7 +51,6 @@ export const BLOCK_COMPONENTS: Record<string, React.ComponentType<{ block: Recor
53
51
  richtext: RichTextBlock,
54
52
 
55
53
  hero: HeroBlock,
56
- "heading-subheading": HeadingSubheadingBlock,
57
54
  card: CardBlock,
58
55
  array: ArrayBlock,
59
56
  relationship: RelationshipBlock,
@@ -61,19 +58,19 @@ export const BLOCK_COMPONENTS: Record<string, React.ComponentType<{ block: Recor
61
58
 
62
59
  // Block Theme mapping for consistent coloring across UI
63
60
  export const blockTheme: Record<string, { text: string; border: string; borderLeft: string }> = {
64
- "feature-split": { text: "text-indigo-500", border: "border-indigo-500", borderLeft: "border-l-indigo-500" },
65
- "feature-grid": { text: "text-blue-500", border: "border-blue-500", borderLeft: "border-l-blue-500" },
66
- "cta-banner": { text: "text-amber-500", border: "border-amber-500", borderLeft: "border-l-amber-500" },
61
+ featureSplit: { text: "text-indigo-500", border: "border-indigo-500", borderLeft: "border-l-indigo-500" },
62
+ featureGrid: { text: "text-blue-500", border: "border-blue-500", borderLeft: "border-l-blue-500" },
63
+ ctaBanner: { text: "text-amber-500", border: "border-amber-500", borderLeft: "border-l-amber-500" },
67
64
  testimonials: { text: "text-emerald-500", border: "border-emerald-500", borderLeft: "border-l-emerald-500" },
68
65
  faq: { text: "text-orange-500", border: "border-orange-500", borderLeft: "border-l-orange-500" },
69
66
  stats: { text: "text-rose-500", border: "border-rose-500", borderLeft: "border-l-rose-500" },
70
- "logo-cloud": { text: "text-cyan-500", border: "border-cyan-500", borderLeft: "border-l-cyan-500" },
67
+ logoCloud: { text: "text-cyan-500", border: "border-cyan-500", borderLeft: "border-l-cyan-500" },
71
68
  pricing: { text: "text-green-500", border: "border-green-500", borderLeft: "border-l-green-500" },
72
69
  team: { text: "text-violet-500", border: "border-violet-500", borderLeft: "border-l-violet-500" },
73
- "recent-feed": { text: "text-sky-500", border: "border-sky-500", borderLeft: "border-l-sky-500" },
74
- "process-steps": { text: "text-fuchsia-500", border: "border-fuchsia-500", borderLeft: "border-l-fuchsia-500" },
75
- "form-embed": { text: "text-pink-500", border: "border-pink-500", borderLeft: "border-l-pink-500" },
76
- "video-showcase": { text: "text-red-500", border: "border-red-500", borderLeft: "border-l-red-500" },
70
+ recentFeed: { text: "text-sky-500", border: "border-sky-500", borderLeft: "border-l-sky-500" },
71
+ processSteps: { text: "text-fuchsia-500", border: "border-fuchsia-500", borderLeft: "border-l-fuchsia-500" },
72
+ formEmbed: { text: "text-pink-500", border: "border-pink-500", borderLeft: "border-l-pink-500" },
73
+ videoShowcase: { text: "text-red-500", border: "border-red-500", borderLeft: "border-l-red-500" },
77
74
  hero: { text: "text-yellow-500", border: "border-yellow-500", borderLeft: "border-l-yellow-500" },
78
75
  card: { text: "text-teal-500", border: "border-teal-500", borderLeft: "border-l-teal-500" },
79
76
  default: { text: "text-zinc-400", border: "border-zinc-400", borderLeft: "border-l-zinc-400" },
@@ -92,25 +89,24 @@ export const blockIcons: Record<string, React.ReactNode> = {
92
89
  richtext: <AlignLeft className={`w-4 h-4 ${blockTheme.default.text}`} />,
93
90
 
94
91
  hero: <Star className={`w-4 h-4 ${blockTheme.hero.text}`} />,
95
- "heading-subheading": <Heading1 className={`w-4 h-4 ${blockTheme.default.text}`} />,
96
92
  card: <Box className={`w-4 h-4 ${blockTheme.card.text}`} />,
97
93
  array: <ListOrdered className={`w-4 h-4 ${blockTheme.default.text}`} />,
98
94
  relationship: <Link2 className={`w-4 h-4 ${blockTheme.default.text}`} />,
99
95
 
100
96
  // New Block Icons
101
- "feature-split": <Columns3 className={`w-4 h-4 ${blockTheme["feature-split"].text}`} />,
102
- "feature-grid": <Blocks className={`w-4 h-4 ${blockTheme["feature-grid"].text}`} />,
103
- "cta-banner": <Sparkles className={`w-4 h-4 ${blockTheme["cta-banner"].text}`} />,
97
+ featureSplit: <Columns3 className={`w-4 h-4 ${blockTheme.featureSplit.text}`} />,
98
+ featureGrid: <Blocks className={`w-4 h-4 ${blockTheme.featureGrid.text}`} />,
99
+ ctaBanner: <Sparkles className={`w-4 h-4 ${blockTheme.ctaBanner.text}`} />,
104
100
  testimonials: <Users className={`w-4 h-4 ${blockTheme.testimonials.text}`} />,
105
101
  faq: <HelpCircle className={`w-4 h-4 ${blockTheme.faq.text}`} />,
106
102
  stats: <Activity className={`w-4 h-4 ${blockTheme.stats.text}`} />,
107
- "logo-cloud": <Image className={`w-4 h-4 ${blockTheme["logo-cloud"].text}`} />,
103
+ logoCloud: <Image className={`w-4 h-4 ${blockTheme.logoCloud.text}`} />,
108
104
  pricing: <Tag className={`w-4 h-4 ${blockTheme.pricing.text}`} />,
109
105
  team: <Users className={`w-4 h-4 ${blockTheme.team.text}`} />,
110
- "recent-feed": <Database className={`w-4 h-4 ${blockTheme["recent-feed"].text}`} />,
111
- "process-steps": <Clock className={`w-4 h-4 ${blockTheme["process-steps"].text}`} />,
112
- "form-embed": <Mail className={`w-4 h-4 ${blockTheme["form-embed"].text}`} />,
113
- "video-showcase": <Video className={`w-4 h-4 ${blockTheme["video-showcase"].text}`} />,
106
+ recentFeed: <Database className={`w-4 h-4 ${blockTheme.recentFeed.text}`} />,
107
+ processSteps: <Clock className={`w-4 h-4 ${blockTheme.processSteps.text}`} />,
108
+ formEmbed: <Mail className={`w-4 h-4 ${blockTheme.formEmbed.text}`} />,
109
+ videoShowcase: <Video className={`w-4 h-4 ${blockTheme.videoShowcase.text}`} />,
114
110
  };
115
111
 
116
112
  // Internal utility to check if block has a specific custom react component
@@ -121,9 +117,9 @@ export function getBlockComponent(type: string) {
121
117
  // Determines if block is a native layout requiring generic rendering
122
118
  export function isGenericSemanticBlock(type: string) {
123
119
  return [
124
- "feature-split", "feature-grid", "cta-banner",
125
- "testimonials", "faq", "stats", "logo-cloud",
126
- "pricing", "team", "recent-feed", "process-steps", "form-embed", "video-showcase"
120
+ "featureSplit", "featureGrid", "ctaBanner",
121
+ "testimonials", "faq", "stats", "logoCloud",
122
+ "pricing", "team", "recentFeed", "processSteps", "formEmbed", "videoShowcase"
127
123
  ].includes(type);
128
124
  }
129
125
 
@@ -145,32 +141,31 @@ export function getBlockLabel(type: string): string {
145
141
 
146
142
  // Core Semantic Blocks
147
143
  hero: "Hero Section",
148
- "heading-subheading": "Heading + Subheading",
149
144
  card: "Card Block",
150
- "feature-split": "Feature Split",
151
- "feature-grid": "Feature Grid",
152
- "cta-banner": "CTA Banner",
145
+ featureSplit: "Feature Split",
146
+ featureGrid: "Feature Grid",
147
+ ctaBanner: "CTA Banner",
153
148
  testimonials: "Testimonials Stack",
154
149
  faq: "FAQ Section",
155
150
  stats: "Stats & Metrics",
156
- "logo-cloud": "Logo Cloud",
151
+ logoCloud: "Logo Cloud",
157
152
 
158
153
  // Brand New Blocks
159
154
  pricing: "Pricing Grid / Plan",
160
155
  team: "Team Profiles Showcase",
161
- "recent-feed": "Dynamic Content Feed",
162
- "process-steps": "Process Timeline / Steps",
163
- "form-embed": "Lead Intake Form",
164
- "video-showcase": "Cinematic Video Showcase",
156
+ recentFeed: "Dynamic Content Feed",
157
+ processSteps: "Process Timeline / Steps",
158
+ formEmbed: "Lead Intake Form",
159
+ videoShowcase: "Cinematic Video Showcase",
165
160
 
166
161
  // Inline Content Elements
167
- "heading-element": "Heading",
168
- "text-element": "Text",
169
- "image-element": "Image",
170
- "richtext-element": "Rich Text",
171
- "button-element": "Button",
172
- "video-element": "Video",
173
- "list-element": "List",
162
+ headingElement: "Heading",
163
+ textElement: "Text",
164
+ imageElement: "Image",
165
+ richtextElement: "Rich Text",
166
+ buttonElement: "Button",
167
+ videoElement: "Video",
168
+ listElement: "List",
174
169
  };
175
170
  return labelMap[type] || type;
176
171
  }