@eventcatalog/core 3.0.0-beta.9 → 3.0.0

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 (148) hide show
  1. package/README.md +41 -98
  2. package/dist/__mocks__/astro-content.cjs +32 -0
  3. package/dist/__mocks__/astro-content.d.cts +13 -0
  4. package/dist/__mocks__/astro-content.d.ts +13 -0
  5. package/dist/__mocks__/astro-content.js +7 -0
  6. package/dist/analytics/analytics.cjs +1 -1
  7. package/dist/analytics/analytics.js +2 -2
  8. package/dist/analytics/log-build.cjs +1 -1
  9. package/dist/analytics/log-build.js +3 -3
  10. package/dist/catalog-to-astro-content-directory.cjs +2 -19
  11. package/dist/catalog-to-astro-content-directory.d.cts +1 -2
  12. package/dist/catalog-to-astro-content-directory.d.ts +1 -2
  13. package/dist/catalog-to-astro-content-directory.js +3 -5
  14. package/dist/{chunk-R2BJ7MJG.js → chunk-6Z6ARMQS.js} +1 -17
  15. package/dist/{chunk-A4MGWK5T.js → chunk-BYP43AAT.js} +1 -1
  16. package/dist/{chunk-RAJ7TGWN.js → chunk-E5Q7TZYT.js} +1 -1
  17. package/dist/{chunk-TT4LZO2Q.js → chunk-EKGR533N.js} +1 -1
  18. package/dist/{chunk-2VPX4WIJ.js → chunk-KF5PARQK.js} +1 -1
  19. package/dist/{chunk-TC3R47V6.js → chunk-VO5WYA44.js} +1 -1
  20. package/dist/constants.cjs +1 -1
  21. package/dist/constants.js +1 -1
  22. package/dist/eventcatalog.cjs +20 -64
  23. package/dist/eventcatalog.config.d.cts +4 -0
  24. package/dist/eventcatalog.config.d.ts +4 -0
  25. package/dist/eventcatalog.js +26 -52
  26. package/dist/generate.cjs +1 -1
  27. package/dist/generate.js +3 -3
  28. package/dist/utils/cli-logger.cjs +1 -1
  29. package/dist/utils/cli-logger.js +2 -2
  30. package/eventcatalog/astro.config.mjs +4 -1
  31. package/eventcatalog/integrations/eventcatalog-features.ts +69 -0
  32. package/eventcatalog/public/icons/asyncapi-black.svg +2 -0
  33. package/eventcatalog/public/icons/graphql-black.svg +1 -0
  34. package/eventcatalog/public/icons/openapi-black.svg +1 -0
  35. package/eventcatalog/src/components/ChatPanel/ChatPanel.tsx +994 -0
  36. package/eventcatalog/src/components/ChatPanel/ChatPanelButton.tsx +24 -0
  37. package/eventcatalog/src/components/Grids/DomainGrid.tsx +310 -173
  38. package/eventcatalog/src/components/Grids/MessageGrid.tsx +299 -180
  39. package/eventcatalog/src/components/Grids/specification-utils.ts +106 -0
  40. package/eventcatalog/src/components/Header.astro +25 -5
  41. package/eventcatalog/src/components/MDX/NodeGraph/NodeGraph.tsx +14 -3
  42. package/eventcatalog/src/components/SchemaExplorer/ApiAccessSection.tsx +95 -90
  43. package/eventcatalog/src/components/SchemaExplorer/ApiContentViewer.tsx +144 -0
  44. package/eventcatalog/src/components/SchemaExplorer/Pagination.tsx +34 -8
  45. package/eventcatalog/src/components/SchemaExplorer/SchemaContentViewer.tsx +2 -2
  46. package/eventcatalog/src/components/SchemaExplorer/SchemaDetailsHeader.tsx +140 -109
  47. package/eventcatalog/src/components/SchemaExplorer/SchemaDetailsPanel.tsx +5 -14
  48. package/eventcatalog/src/components/SchemaExplorer/SchemaExplorer.tsx +247 -59
  49. package/eventcatalog/src/components/SchemaExplorer/SchemaFilters.tsx +64 -126
  50. package/eventcatalog/src/components/SchemaExplorer/SchemaListItem.tsx +41 -43
  51. package/eventcatalog/src/components/Search/Search.astro +2 -2
  52. package/eventcatalog/src/components/Search/SearchDataLoader.astro +25 -0
  53. package/eventcatalog/src/components/SideNav/NestedSideBar/SearchBar.tsx +6 -3
  54. package/eventcatalog/src/components/SideNav/NestedSideBar/index.tsx +44 -16
  55. package/eventcatalog/src/components/SideNav/SideNav.astro +0 -15
  56. package/eventcatalog/src/components/Tables/Table.tsx +96 -77
  57. package/eventcatalog/src/components/Tables/columns/ContainersTableColumns.tsx +108 -74
  58. package/eventcatalog/src/components/Tables/columns/DomainTableColumns.tsx +74 -55
  59. package/eventcatalog/src/components/Tables/columns/FlowTableColumns.tsx +36 -36
  60. package/eventcatalog/src/components/Tables/columns/MessageTableColumns.tsx +110 -77
  61. package/eventcatalog/src/components/Tables/columns/ServiceTableColumns.tsx +105 -94
  62. package/eventcatalog/src/components/Tables/columns/SharedColumns.tsx +31 -26
  63. package/eventcatalog/src/components/Tables/columns/TeamsTableColumns.tsx +115 -215
  64. package/eventcatalog/src/components/Tables/columns/UserTableColumns.tsx +145 -243
  65. package/eventcatalog/src/content.config.ts +1 -13
  66. package/eventcatalog/src/enterprise/ai/chat-api.ts +360 -0
  67. package/eventcatalog/src/enterprise/auth/[...auth].ts +3 -0
  68. package/eventcatalog/src/enterprise/auth/login.astro +420 -0
  69. package/eventcatalog/src/enterprise/collections/index.ts +0 -1
  70. package/eventcatalog/src/layouts/Footer.astro +8 -5
  71. package/eventcatalog/src/layouts/VerticalSideBarLayout.astro +133 -117
  72. package/eventcatalog/src/pages/_index.astro +243 -559
  73. package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/_index.data.ts +8 -2
  74. package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/index.astro +9 -5
  75. package/eventcatalog/src/pages/directory/[type]/index.astro +6 -0
  76. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/asyncapi/[filename].astro +19 -3
  77. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/changelog/index.astro +7 -7
  78. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/graphql/[filename].astro +1 -1
  79. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +10 -7
  80. package/eventcatalog/src/pages/docs/[type]/[id]/language/index.astro +194 -121
  81. package/eventcatalog/src/pages/docs/teams/[id]/index.astro +94 -70
  82. package/eventcatalog/src/pages/docs/teams/[id].mdx.ts +36 -0
  83. package/eventcatalog/src/pages/docs/users/[id]/index.astro +56 -45
  84. package/eventcatalog/src/pages/docs/users/[id].mdx.ts +36 -0
  85. package/eventcatalog/src/pages/schemas/explorer/_index.data.ts +178 -0
  86. package/eventcatalog/src/pages/schemas/explorer/index.astro +7 -157
  87. package/eventcatalog/src/pages/studio.astro +124 -72
  88. package/eventcatalog/src/remark-plugins/directives.ts +30 -9
  89. package/eventcatalog/src/{components/SideNav/NestedSideBar → stores/sidebar-store}/builders/container.ts +10 -1
  90. package/eventcatalog/src/{components/SideNav/NestedSideBar → stores/sidebar-store}/builders/domain.ts +17 -7
  91. package/eventcatalog/src/{components/SideNav/NestedSideBar → stores/sidebar-store}/builders/message.ts +10 -1
  92. package/eventcatalog/src/{components/SideNav/NestedSideBar → stores/sidebar-store}/builders/service.ts +11 -4
  93. package/eventcatalog/src/{components/SideNav/NestedSideBar → stores/sidebar-store}/builders/shared.ts +14 -0
  94. package/eventcatalog/src/stores/{sidebar-store.ts → sidebar-store/index.ts} +1 -1
  95. package/eventcatalog/src/utils/collections/channels.ts +0 -2
  96. package/eventcatalog/src/utils/collections/commands.ts +0 -2
  97. package/eventcatalog/src/utils/collections/containers.ts +0 -2
  98. package/eventcatalog/src/utils/collections/domains.ts +0 -2
  99. package/eventcatalog/src/utils/collections/entities.ts +0 -2
  100. package/eventcatalog/src/utils/collections/events.ts +0 -2
  101. package/eventcatalog/src/utils/collections/flows.ts +0 -2
  102. package/eventcatalog/src/utils/collections/queries.ts +0 -2
  103. package/eventcatalog/src/utils/collections/schemas.ts +45 -7
  104. package/eventcatalog/src/utils/collections/services.ts +0 -2
  105. package/eventcatalog/src/utils/feature.ts +9 -5
  106. package/eventcatalog/src/utils/node-graphs/services-node-graph.ts +1 -1
  107. package/eventcatalog/src/utils/resource-files.ts +86 -0
  108. package/package.json +12 -15
  109. package/default-files-for-collections/changelogs.md +0 -5
  110. package/default-files-for-collections/channels.md +0 -8
  111. package/default-files-for-collections/commands.md +0 -8
  112. package/default-files-for-collections/domains.md +0 -8
  113. package/default-files-for-collections/events.md +0 -8
  114. package/default-files-for-collections/flows.md +0 -11
  115. package/default-files-for-collections/queries.md +0 -8
  116. package/default-files-for-collections/services.md +0 -8
  117. package/default-files-for-collections/ubiquitousLanguages.md +0 -7
  118. package/eventcatalog/src/enterprise/collections/chat-prompts.ts +0 -32
  119. package/eventcatalog/src/enterprise/eventcatalog-chat/components/Chat.tsx +0 -60
  120. package/eventcatalog/src/enterprise/eventcatalog-chat/components/ChatMessage.tsx +0 -414
  121. package/eventcatalog/src/enterprise/eventcatalog-chat/components/ChatSidebar.tsx +0 -169
  122. package/eventcatalog/src/enterprise/eventcatalog-chat/components/InputModal.tsx +0 -244
  123. package/eventcatalog/src/enterprise/eventcatalog-chat/components/MentionInput.tsx +0 -211
  124. package/eventcatalog/src/enterprise/eventcatalog-chat/components/WelcomePromptArea.tsx +0 -176
  125. package/eventcatalog/src/enterprise/eventcatalog-chat/components/default-prompts.ts +0 -93
  126. package/eventcatalog/src/enterprise/eventcatalog-chat/components/hooks/ChatProvider.tsx +0 -143
  127. package/eventcatalog/src/enterprise/eventcatalog-chat/components/windows/ChatWindow.server.tsx +0 -387
  128. package/eventcatalog/src/enterprise/eventcatalog-chat/pages/api/chat.ts +0 -59
  129. package/eventcatalog/src/enterprise/eventcatalog-chat/pages/chat/index.astro +0 -104
  130. package/eventcatalog/src/enterprise/eventcatalog-chat/providers/ai-provider.ts +0 -140
  131. package/eventcatalog/src/enterprise/eventcatalog-chat/providers/anthropic.ts +0 -28
  132. package/eventcatalog/src/enterprise/eventcatalog-chat/providers/google.ts +0 -41
  133. package/eventcatalog/src/enterprise/eventcatalog-chat/providers/index.ts +0 -26
  134. package/eventcatalog/src/enterprise/eventcatalog-chat/providers/openai.ts +0 -61
  135. package/eventcatalog/src/enterprise/eventcatalog-chat/utils/chat-prompts.ts +0 -50
  136. package/eventcatalog/src/pages/auth/login.astro +0 -280
  137. package/eventcatalog/src/pages/chat/feature.astro +0 -179
  138. package/eventcatalog/src/pages/chat/index.astro +0 -10
  139. package/eventcatalog/src/pages/docs/_default-docs.mdx +0 -25
  140. package/eventcatalog/src/pages/docs/index.astro +0 -33
  141. package/eventcatalog/src/pages/nav-index.json.ts +0 -30
  142. /package/eventcatalog/src/{pages → enterprise}/auth/error.astro +0 -0
  143. /package/eventcatalog/src/{middleware-auth.ts → enterprise/auth/middleware/middleware-auth.ts} +0 -0
  144. /package/eventcatalog/src/{middleware.ts → enterprise/auth/middleware/middleware.ts} +0 -0
  145. /package/eventcatalog/src/{pages/unauthorized/index.astro → enterprise/auth/unauthorized.astro} +0 -0
  146. /package/eventcatalog/src/{pages → enterprise}/plans/index.astro +0 -0
  147. /package/eventcatalog/src/{components/SideNav/NestedSideBar → stores/sidebar-store}/builders/flow.ts +0 -0
  148. /package/eventcatalog/src/{components/SideNav/NestedSideBar/sidebar-builder.ts → stores/sidebar-store/state.ts} +0 -0
@@ -1,8 +1,16 @@
1
1
  import { useState, useMemo, useEffect, useRef } from 'react';
2
- import { DocumentTextIcon, FunnelIcon } from '@heroicons/react/24/outline';
2
+ import { DocumentTextIcon, MagnifyingGlassIcon, XMarkIcon } from '@heroicons/react/24/outline';
3
+ import {
4
+ BoltIcon,
5
+ ChatBubbleLeftIcon,
6
+ MagnifyingGlassIcon as MagnifyingGlassSolidIcon,
7
+ CodeBracketIcon,
8
+ } from '@heroicons/react/24/solid';
3
9
  import type { CollectionMessageTypes } from '@types';
10
+
11
+ // Specification file types (OpenAPI, AsyncAPI, GraphQL)
12
+ const SPEC_TYPES = ['openapi', 'asyncapi', 'graphql'];
4
13
  import semver from 'semver';
5
- import SchemaFilters from './SchemaFilters';
6
14
  import SchemaListItem from './SchemaListItem';
7
15
  import SchemaDetailsPanel from './SchemaDetailsPanel';
8
16
  import Pagination from './Pagination';
@@ -22,13 +30,20 @@ export default function SchemaExplorer({ schemas, apiAccessEnabled = false }: Sc
22
30
  }
23
31
  return '';
24
32
  });
25
- const [selectedType, setSelectedType] = useState<'all' | CollectionMessageTypes | 'services'>(() => {
33
+ const [selectedTypes, setSelectedTypes] = useState<Set<CollectionMessageTypes | 'specifications'>>(() => {
26
34
  // Load from localStorage
27
35
  if (typeof window !== 'undefined') {
28
- const stored = localStorage.getItem('schemaRegistrySelectedType');
29
- return stored !== null ? (stored as 'all' | CollectionMessageTypes | 'services') : 'all';
36
+ const stored = localStorage.getItem('schemaRegistrySelectedTypes');
37
+ if (stored) {
38
+ try {
39
+ const parsed = JSON.parse(stored);
40
+ return new Set(parsed);
41
+ } catch {
42
+ return new Set();
43
+ }
44
+ }
30
45
  }
31
- return 'all';
46
+ return new Set();
32
47
  });
33
48
  const [selectedSchemaType, setSelectedSchemaType] = useState<'all' | string>(() => {
34
49
  // Load from localStorage
@@ -41,23 +56,10 @@ export default function SchemaExplorer({ schemas, apiAccessEnabled = false }: Sc
41
56
  const [selectedMessage, setSelectedMessage] = useState<SchemaItem | null>(null);
42
57
  const [selectedVersion, setSelectedVersion] = useState<string | null>(null);
43
58
  const [currentPage, setCurrentPage] = useState(1);
44
- const [filtersExpanded, setFiltersExpanded] = useState(() => {
45
- if (typeof window !== 'undefined') {
46
- const stored = localStorage.getItem('schemaRegistryFiltersExpanded');
47
- return stored !== null ? stored === 'true' : true;
48
- }
49
- return true;
50
- });
51
- const [isMounted, setIsMounted] = useState(false);
52
59
  const searchInputRef = useRef<HTMLInputElement>(null);
53
60
  const selectedItemRef = useRef<HTMLButtonElement>(null);
54
61
  const ITEMS_PER_PAGE = 50;
55
62
 
56
- // Set mounted state after hydration to prevent FOUC
57
- useEffect(() => {
58
- setIsMounted(true);
59
- }, []);
60
-
61
63
  // Function to update URL with query params
62
64
  const updateUrlParams = (message: SchemaItem) => {
63
65
  if (typeof window === 'undefined') return;
@@ -138,9 +140,19 @@ export default function SchemaExplorer({ schemas, apiAccessEnabled = false }: Sc
138
140
  const filteredMessages = useMemo(() => {
139
141
  let result = [...latestMessages];
140
142
 
141
- // Filter by message type
142
- if (selectedType !== 'all') {
143
- result = result.filter((msg) => msg.collection === selectedType);
143
+ // Filter by message types (multi-select)
144
+ if (selectedTypes.size > 0) {
145
+ result = result.filter((msg) => {
146
+ // Check if message matches any selected collection type
147
+ if (selectedTypes.has(msg.collection as CollectionMessageTypes)) {
148
+ return true;
149
+ }
150
+ // Check if 'specifications' is selected and this is a spec file
151
+ if (selectedTypes.has('specifications') && SPEC_TYPES.includes(msg.schemaExtension?.toLowerCase() || '')) {
152
+ return true;
153
+ }
154
+ return false;
155
+ });
144
156
  }
145
157
 
146
158
  // Filter by schema type
@@ -167,7 +179,7 @@ export default function SchemaExplorer({ schemas, apiAccessEnabled = false }: Sc
167
179
  });
168
180
 
169
181
  return result;
170
- }, [latestMessages, searchQuery, selectedType, selectedSchemaType]);
182
+ }, [latestMessages, searchQuery, selectedTypes, selectedSchemaType]);
171
183
 
172
184
  // Pagination
173
185
  const totalPages = Math.ceil(filteredMessages.length / ITEMS_PER_PAGE);
@@ -178,7 +190,7 @@ export default function SchemaExplorer({ schemas, apiAccessEnabled = false }: Sc
178
190
 
179
191
  useEffect(() => {
180
192
  setCurrentPage(1);
181
- }, [searchQuery, selectedType, selectedSchemaType]);
193
+ }, [searchQuery, selectedTypes, selectedSchemaType]);
182
194
 
183
195
  // Load from query string on mount
184
196
  useEffect(() => {
@@ -255,13 +267,6 @@ export default function SchemaExplorer({ schemas, apiAccessEnabled = false }: Sc
255
267
  return versionedMessage || versions[0];
256
268
  }, [selectedMessage, selectedVersion, messagesByIdAndVersions]);
257
269
 
258
- // Save filter expanded state to localStorage
259
- useEffect(() => {
260
- if (typeof window !== 'undefined') {
261
- localStorage.setItem('schemaRegistryFiltersExpanded', filtersExpanded.toString());
262
- }
263
- }, [filtersExpanded]);
264
-
265
270
  // Save filter states to localStorage
266
271
  useEffect(() => {
267
272
  if (typeof window !== 'undefined') {
@@ -271,9 +276,9 @@ export default function SchemaExplorer({ schemas, apiAccessEnabled = false }: Sc
271
276
 
272
277
  useEffect(() => {
273
278
  if (typeof window !== 'undefined') {
274
- localStorage.setItem('schemaRegistrySelectedType', selectedType);
279
+ localStorage.setItem('schemaRegistrySelectedTypes', JSON.stringify(Array.from(selectedTypes)));
275
280
  }
276
- }, [selectedType]);
281
+ }, [selectedTypes]);
277
282
 
278
283
  useEffect(() => {
279
284
  if (typeof window !== 'undefined') {
@@ -312,32 +317,194 @@ export default function SchemaExplorer({ schemas, apiAccessEnabled = false }: Sc
312
317
  }
313
318
  };
314
319
 
320
+ // Calculate stats
321
+ const stats = useMemo(() => {
322
+ return {
323
+ total: latestMessages.length,
324
+ events: latestMessages.filter((m) => m.collection === 'events').length,
325
+ commands: latestMessages.filter((m) => m.collection === 'commands').length,
326
+ queries: latestMessages.filter((m) => m.collection === 'queries').length,
327
+ specifications: latestMessages.filter((m) => SPEC_TYPES.includes(m.schemaExtension?.toLowerCase() || '')).length,
328
+ };
329
+ }, [latestMessages]);
330
+
331
+ // Toggle type selection (multi-select)
332
+ const toggleType = (type: CollectionMessageTypes | 'specifications') => {
333
+ setSelectedTypes((prev) => {
334
+ const next = new Set(prev);
335
+ if (next.has(type)) {
336
+ next.delete(type);
337
+ } else {
338
+ next.add(type);
339
+ }
340
+ return next;
341
+ });
342
+ };
343
+
344
+ // Clear all type filters
345
+ const clearTypeFilters = () => {
346
+ setSelectedTypes(new Set());
347
+ };
348
+
315
349
  return (
316
350
  <div className="h-full flex flex-col overflow-hidden">
317
- {/* Split View */}
351
+ {/* Split View - Full Height */}
318
352
  <div className="flex-1 flex gap-4 overflow-hidden">
319
- {/* Left: Filters + Schema List */}
320
- <div className="w-1/3 flex flex-col bg-white border border-gray-200 rounded-lg overflow-hidden">
321
- {/* Filters */}
322
- <SchemaFilters
323
- searchQuery={searchQuery}
324
- onSearchChange={setSearchQuery}
325
- selectedType={selectedType}
326
- onTypeChange={setSelectedType}
327
- selectedSchemaType={selectedSchemaType}
328
- onSchemaTypeChange={setSelectedSchemaType}
329
- schemaTypes={schemaTypes}
330
- latestMessages={latestMessages}
331
- filtersExpanded={filtersExpanded}
332
- onToggleExpanded={() => setFiltersExpanded(!filtersExpanded)}
333
- searchInputRef={searchInputRef}
334
- isMounted={isMounted}
335
- />
353
+ {/* Left: Schema List */}
354
+ <div className="w-[320px] flex-shrink-0 flex flex-col bg-white border border-gray-200 rounded-lg overflow-hidden shadow-sm">
355
+ {/* Search Header */}
356
+ <div className="flex-shrink-0 p-3 border-b border-gray-200">
357
+ {/* Search + Format Filter Row */}
358
+ <div className="flex items-center gap-2">
359
+ <div className="relative flex-1">
360
+ <div className="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
361
+ <MagnifyingGlassIcon className="h-4 w-4 text-gray-400" />
362
+ </div>
363
+ <input
364
+ ref={searchInputRef}
365
+ type="text"
366
+ placeholder="Search schemas..."
367
+ value={searchQuery}
368
+ onChange={(e) => setSearchQuery(e.target.value)}
369
+ className="w-full rounded-md border border-gray-200 bg-white py-2 pl-9 pr-8 text-sm placeholder:text-gray-400 focus:border-gray-300 focus:outline-none focus:ring-1 focus:ring-gray-300 transition-all"
370
+ />
371
+ {searchQuery && (
372
+ <button onClick={() => setSearchQuery('')} className="absolute inset-y-0 right-0 flex items-center pr-2.5">
373
+ <XMarkIcon className="h-4 w-4 text-gray-400 hover:text-gray-600" />
374
+ </button>
375
+ )}
376
+ </div>
377
+ {/* Format Dropdown */}
378
+ {schemaTypes.length > 1 && (
379
+ <select
380
+ value={selectedSchemaType}
381
+ onChange={(e) => setSelectedSchemaType(e.target.value)}
382
+ className="flex-shrink-0 text-xs font-medium text-gray-600 bg-white border border-gray-200 rounded-md px-2 py-2 focus:outline-none focus:ring-1 focus:ring-gray-300 focus:border-gray-300 cursor-pointer hover:bg-gray-50 transition-colors"
383
+ >
384
+ <option value="all">All formats</option>
385
+ {schemaTypes.map((type) => {
386
+ const labels: Record<string, string> = {
387
+ json: 'JSON',
388
+ asyncapi: 'AsyncAPI',
389
+ openapi: 'OpenAPI',
390
+ graphql: 'GraphQL',
391
+ avro: 'Avro',
392
+ proto: 'Protobuf',
393
+ };
394
+ return (
395
+ <option key={type} value={type}>
396
+ {labels[type] || type.charAt(0).toUpperCase() + type.slice(1)}
397
+ </option>
398
+ );
399
+ })}
400
+ </select>
401
+ )}
402
+ </div>
403
+
404
+ {/* Type Filter - Multi-select chips */}
405
+ <div className="flex items-center gap-1 mt-2 flex-wrap">
406
+ {stats.events > 0 && (
407
+ <button
408
+ onClick={() => toggleType('events')}
409
+ className={`inline-flex items-center gap-1.5 px-2 py-1 rounded-md text-xs font-medium transition-all border ${
410
+ selectedTypes.has('events')
411
+ ? 'bg-orange-50 text-orange-700 border-orange-200'
412
+ : 'text-gray-600 border-gray-200 hover:bg-gray-50'
413
+ }`}
414
+ title="Events"
415
+ >
416
+ <BoltIcon className={`h-3.5 w-3.5 ${selectedTypes.has('events') ? 'text-orange-500' : 'text-orange-400'}`} />
417
+ <span>Events</span>
418
+ <span className={`tabular-nums ${selectedTypes.has('events') ? 'text-orange-500' : 'text-gray-400'}`}>
419
+ {stats.events}
420
+ </span>
421
+ </button>
422
+ )}
423
+ {stats.commands > 0 && (
424
+ <button
425
+ onClick={() => toggleType('commands')}
426
+ className={`inline-flex items-center gap-1.5 px-2 py-1 rounded-md text-xs font-medium transition-all border ${
427
+ selectedTypes.has('commands')
428
+ ? 'bg-blue-50 text-blue-700 border-blue-200'
429
+ : 'text-gray-600 border-gray-200 hover:bg-gray-50'
430
+ }`}
431
+ title="Commands"
432
+ >
433
+ <ChatBubbleLeftIcon
434
+ className={`h-3.5 w-3.5 ${selectedTypes.has('commands') ? 'text-blue-500' : 'text-blue-400'}`}
435
+ />
436
+ <span>Commands</span>
437
+ <span className={`tabular-nums ${selectedTypes.has('commands') ? 'text-blue-500' : 'text-gray-400'}`}>
438
+ {stats.commands}
439
+ </span>
440
+ </button>
441
+ )}
442
+ {stats.queries > 0 && (
443
+ <button
444
+ onClick={() => toggleType('queries')}
445
+ className={`inline-flex items-center gap-1.5 px-2 py-1 rounded-md text-xs font-medium transition-all border ${
446
+ selectedTypes.has('queries')
447
+ ? 'bg-green-50 text-green-700 border-green-200'
448
+ : 'text-gray-600 border-gray-200 hover:bg-gray-50'
449
+ }`}
450
+ title="Queries"
451
+ >
452
+ <MagnifyingGlassSolidIcon
453
+ className={`h-3.5 w-3.5 ${selectedTypes.has('queries') ? 'text-green-500' : 'text-green-400'}`}
454
+ />
455
+ <span>Queries</span>
456
+ <span className={`tabular-nums ${selectedTypes.has('queries') ? 'text-green-500' : 'text-gray-400'}`}>
457
+ {stats.queries}
458
+ </span>
459
+ </button>
460
+ )}
461
+ {stats.specifications > 0 && (
462
+ <button
463
+ onClick={() => toggleType('specifications')}
464
+ className={`inline-flex items-center gap-1.5 px-2 py-1 rounded-md text-xs font-medium transition-all border ${
465
+ selectedTypes.has('specifications')
466
+ ? 'bg-purple-50 text-purple-700 border-purple-200'
467
+ : 'text-gray-600 border-gray-200 hover:bg-gray-50'
468
+ }`}
469
+ title="Specifications (OpenAPI, AsyncAPI, etc.)"
470
+ >
471
+ <CodeBracketIcon
472
+ className={`h-3.5 w-3.5 ${selectedTypes.has('specifications') ? 'text-purple-500' : 'text-purple-400'}`}
473
+ />
474
+ <span>Specs</span>
475
+ <span className={`tabular-nums ${selectedTypes.has('specifications') ? 'text-purple-500' : 'text-gray-400'}`}>
476
+ {stats.specifications}
477
+ </span>
478
+ </button>
479
+ )}
480
+ </div>
481
+ </div>
482
+
483
+ {/* Results Count Bar */}
484
+ <div className="flex-shrink-0 px-3 py-1.5 bg-gray-50 border-b border-gray-100 flex items-center justify-between">
485
+ <span className="text-xs text-gray-500">
486
+ {filteredMessages.length === stats.total
487
+ ? `${stats.total} schemas`
488
+ : `${filteredMessages.length} of ${stats.total} schemas`}
489
+ </span>
490
+ {(searchQuery || selectedTypes.size > 0 || selectedSchemaType !== 'all') && (
491
+ <button
492
+ onClick={() => {
493
+ setSearchQuery('');
494
+ clearTypeFilters();
495
+ setSelectedSchemaType('all');
496
+ }}
497
+ className="text-xs text-gray-500 hover:text-gray-700"
498
+ >
499
+ Clear filters
500
+ </button>
501
+ )}
502
+ </div>
336
503
 
337
504
  {/* Schema List - Independently Scrollable */}
338
505
  <div className="flex-1 overflow-y-auto">
339
506
  {paginatedMessages.length > 0 ? (
340
- <div className="divide-y divide-gray-200">
507
+ <div className="divide-y divide-gray-100">
341
508
  {paginatedMessages.map((message) => {
342
509
  // For services, also check spec type to determine if selected
343
510
  const isSelected =
@@ -368,10 +535,26 @@ export default function SchemaExplorer({ schemas, apiAccessEnabled = false }: Sc
368
535
  })}
369
536
  </div>
370
537
  ) : (
371
- <div className="flex flex-col items-center justify-center h-full p-8 text-center">
372
- <FunnelIcon className="h-12 w-12 text-gray-400 mb-3" />
538
+ <div className="flex flex-col items-center justify-center h-full p-6 text-center">
539
+ <div className="flex items-center justify-center w-12 h-12 rounded-full bg-gray-100 mb-3">
540
+ <MagnifyingGlassIcon className="h-6 w-6 text-gray-400" />
541
+ </div>
373
542
  <h3 className="text-sm font-semibold text-gray-900 mb-1">No schemas found</h3>
374
- <p className="text-xs text-gray-500">Try adjusting your filters</p>
543
+ <p className="text-xs text-gray-500 mb-3 max-w-[200px]">
544
+ {searchQuery ? `No results for "${searchQuery}"` : 'Try adjusting your filters'}
545
+ </p>
546
+ {(searchQuery || selectedTypes.size > 0 || selectedSchemaType !== 'all') && (
547
+ <button
548
+ onClick={() => {
549
+ setSearchQuery('');
550
+ clearTypeFilters();
551
+ setSelectedSchemaType('all');
552
+ }}
553
+ className="text-xs font-medium text-gray-600 hover:text-gray-900"
554
+ >
555
+ Clear filters
556
+ </button>
557
+ )}
375
558
  </div>
376
559
  )}
377
560
  </div>
@@ -391,10 +574,15 @@ export default function SchemaExplorer({ schemas, apiAccessEnabled = false }: Sc
391
574
  apiAccessEnabled={apiAccessEnabled}
392
575
  />
393
576
  ) : (
394
- <div className="h-full flex items-center justify-center text-gray-500">
395
- <div className="text-center">
396
- <DocumentTextIcon className="h-12 w-12 mx-auto mb-3 text-gray-400" />
397
- <p>Select a schema to view details</p>
577
+ <div className="h-full flex items-center justify-center">
578
+ <div className="text-center max-w-xs">
579
+ <div className="flex items-center justify-center w-14 h-14 mx-auto mb-4 rounded-xl bg-gray-50 border border-gray-100">
580
+ <DocumentTextIcon className="h-7 w-7 text-gray-400" />
581
+ </div>
582
+ <h3 className="text-sm font-semibold text-gray-900 mb-1">Select a schema</h3>
583
+ <p className="text-sm text-gray-500 leading-relaxed">
584
+ Choose a schema from the list to view details, compare versions, and access raw code
585
+ </p>
398
586
  </div>
399
587
  </div>
400
588
  )}
@@ -1,171 +1,109 @@
1
- import { MagnifyingGlassIcon, XMarkIcon, FunnelIcon, ChevronUpIcon, ChevronDownIcon } from '@heroicons/react/24/outline';
2
- import type { CollectionMessageTypes } from '@types';
1
+ import { AdjustmentsHorizontalIcon, ChevronUpIcon, ChevronDownIcon } from '@heroicons/react/24/outline';
2
+ import { buildUrl } from '@utils/url-builder';
3
3
  import { getSchemaTypeLabel } from './utils';
4
4
  import type { SchemaItem } from './types';
5
5
 
6
6
  interface SchemaFiltersProps {
7
- searchQuery: string;
8
- onSearchChange: (query: string) => void;
9
- selectedType: 'all' | CollectionMessageTypes | 'services';
10
- onTypeChange: (type: 'all' | CollectionMessageTypes | 'services') => void;
11
7
  selectedSchemaType: string;
12
8
  onSchemaTypeChange: (type: string) => void;
13
9
  schemaTypes: string[];
14
10
  latestMessages: SchemaItem[];
15
11
  filtersExpanded: boolean;
16
12
  onToggleExpanded: () => void;
17
- searchInputRef: React.RefObject<HTMLInputElement>;
18
13
  isMounted: boolean;
19
14
  }
20
15
 
21
16
  export default function SchemaFilters({
22
- searchQuery,
23
- onSearchChange,
24
- selectedType,
25
- onTypeChange,
26
17
  selectedSchemaType,
27
18
  onSchemaTypeChange,
28
19
  schemaTypes,
29
20
  latestMessages,
30
21
  filtersExpanded,
31
22
  onToggleExpanded,
32
- searchInputRef,
33
23
  isMounted,
34
24
  }: SchemaFiltersProps) {
35
- const activeFilterCount = [searchQuery, selectedType !== 'all', selectedSchemaType !== 'all'].filter(Boolean).length;
25
+ const hasActiveFilters = selectedSchemaType !== 'all';
26
+
27
+ // Only show this component if there are schema types to filter
28
+ if (schemaTypes.length <= 1) {
29
+ return null;
30
+ }
36
31
 
37
32
  return (
38
- <div className="flex-shrink-0 border-b border-gray-200 bg-gray-50">
33
+ <div className="flex-shrink-0 border-b border-gray-100 bg-white">
39
34
  {/* Filter Header */}
40
35
  <button
41
36
  onClick={onToggleExpanded}
42
- className="w-full flex items-center justify-between p-3 hover:bg-gray-100 transition-colors"
37
+ className="w-full flex items-center justify-between px-3 py-1.5 hover:bg-gray-50 transition-colors"
43
38
  >
44
- <div className="flex items-center gap-2">
45
- <FunnelIcon className="h-4 w-4 text-gray-600" />
46
- <span className="text-xs font-semibold text-gray-900">Filters</span>
47
- {activeFilterCount > 0 && (
48
- <span className="inline-flex items-center rounded-full bg-primary px-2 py-0.5 text-xs font-medium text-white">
49
- {activeFilterCount}
39
+ <div className="flex items-center gap-1.5">
40
+ <AdjustmentsHorizontalIcon className="h-3.5 w-3.5 text-gray-400" />
41
+ <span className="text-[11px] font-medium text-gray-600">Format</span>
42
+ {hasActiveFilters && (
43
+ <span className="inline-flex items-center justify-center w-4 h-4 rounded-full bg-gray-900 text-[10px] font-medium text-white">
44
+ 1
50
45
  </span>
51
46
  )}
52
47
  </div>
53
48
  {filtersExpanded ? (
54
- <ChevronUpIcon className="h-4 w-4 text-gray-600" />
49
+ <ChevronUpIcon className="h-3.5 w-3.5 text-gray-400" />
55
50
  ) : (
56
- <ChevronDownIcon className="h-4 w-4 text-gray-600" />
51
+ <ChevronDownIcon className="h-3.5 w-3.5 text-gray-400" />
57
52
  )}
58
53
  </button>
59
54
 
60
- {/* Collapsible Filter Content - Only render after mount to prevent FOUC */}
55
+ {/* Collapsible Filter Content */}
61
56
  {isMounted && filtersExpanded && (
62
- <div className="p-3 pt-0">
63
- {/* Search */}
64
- <div className="mb-3">
65
- <label htmlFor="search" className="block text-xs font-medium text-gray-700 mb-1.5">
66
- Search
67
- </label>
68
- <div className="relative">
69
- <div className="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-2.5">
70
- <MagnifyingGlassIcon className="h-4 w-4 text-gray-400" />
71
- </div>
72
- <input
73
- ref={searchInputRef}
74
- type="text"
75
- id="search"
76
- placeholder="Search schemas..."
77
- value={searchQuery}
78
- onChange={(e) => onSearchChange(e.target.value)}
79
- className="w-full rounded-md border-gray-300 shadow-sm focus:border-primary focus:ring-primary text-xs pl-8 pr-8 py-1.5 border"
80
- />
81
- {searchQuery && (
82
- <button onClick={() => onSearchChange('')} className="absolute inset-y-0 right-0 flex items-center pr-2.5">
83
- <XMarkIcon className="h-4 w-4 text-gray-400 hover:text-gray-600" />
84
- </button>
85
- )}
86
- </div>
87
- </div>
88
-
89
- {/* Message Type Filter */}
90
- <div className="mb-3">
91
- <label htmlFor="messageType" className="block text-xs font-medium text-gray-700 mb-1.5">
92
- Message Type
93
- </label>
94
- <select
95
- id="messageType"
96
- value={selectedType}
97
- onChange={(e) => onTypeChange(e.target.value as typeof selectedType)}
98
- className="w-full rounded-md border-gray-300 shadow-sm focus:border-primary focus:ring-primary text-xs px-2.5 py-1.5 border"
99
- >
100
- <option value="all">All ({latestMessages.length})</option>
101
- <option value="events">Events ({latestMessages.filter((m) => m.collection === 'events').length})</option>
102
- <option value="commands">Commands ({latestMessages.filter((m) => m.collection === 'commands').length})</option>
103
- <option value="queries">Queries ({latestMessages.filter((m) => m.collection === 'queries').length})</option>
104
- <option value="services">Services ({latestMessages.filter((m) => m.collection === 'services').length})</option>
105
- </select>
106
- </div>
107
-
108
- {/* Schema Type Filter */}
109
- <div className="mb-3">
110
- <label htmlFor="schemaType" className="block text-xs font-medium text-gray-700 mb-1.5">
111
- Schema Format
112
- </label>
113
- <select
114
- id="schemaType"
115
- value={selectedSchemaType}
116
- onChange={(e) => onSchemaTypeChange(e.target.value)}
117
- className="w-full rounded-md border-gray-300 shadow-sm focus:border-primary focus:ring-primary text-xs px-2.5 py-1.5 border"
57
+ <div className="px-3 pb-2">
58
+ {/* Schema Format Filter as chips */}
59
+ <div className="flex flex-wrap gap-1">
60
+ <button
61
+ onClick={() => onSchemaTypeChange('all')}
62
+ className={`inline-flex items-center px-2 py-0.5 rounded text-[11px] font-medium transition-all ${
63
+ selectedSchemaType === 'all'
64
+ ? 'bg-gray-900 text-white'
65
+ : 'bg-gray-50 text-gray-600 border border-gray-200 hover:bg-gray-100'
66
+ }`}
118
67
  >
119
- <option value="all">All Formats</option>
120
- {schemaTypes.map((type) => (
121
- <option key={type} value={type}>
122
- {getSchemaTypeLabel(type)} ({latestMessages.filter((m) => m.schemaExtension?.toLowerCase() === type).length})
123
- </option>
124
- ))}
125
- </select>
126
- </div>
68
+ All
69
+ </button>
70
+ {schemaTypes.map((type) => {
71
+ const count = latestMessages.filter((m) => m.schemaExtension?.toLowerCase() === type).length;
72
+ const ext = type.toLowerCase();
73
+ const hasIcon = ['openapi', 'asyncapi', 'graphql', 'avro', 'json', 'proto'].includes(ext);
74
+ const iconName = ext === 'json' ? 'json-schema' : ext;
127
75
 
128
- {/* Active filters */}
129
- {activeFilterCount > 0 && (
130
- <div className="pt-2 border-t border-gray-200">
131
- <div className="flex flex-wrap items-center gap-1.5">
132
- {searchQuery && (
133
- <span className="inline-flex items-center gap-1 px-1.5 py-0.5 text-xs bg-blue-100 text-blue-800 rounded">
134
- {searchQuery.substring(0, 15)}
135
- {searchQuery.length > 15 ? '...' : ''}
136
- <button onClick={() => onSearchChange('')}>
137
- <XMarkIcon className="h-3 w-3" />
138
- </button>
139
- </span>
140
- )}
141
- {selectedType !== 'all' && (
142
- <span className="inline-flex items-center gap-1 px-1.5 py-0.5 text-xs bg-blue-100 text-blue-800 rounded">
143
- {selectedType}
144
- <button onClick={() => onTypeChange('all')}>
145
- <XMarkIcon className="h-3 w-3" />
146
- </button>
147
- </span>
148
- )}
149
- {selectedSchemaType !== 'all' && (
150
- <span className="inline-flex items-center gap-1 px-1.5 py-0.5 text-xs bg-blue-100 text-blue-800 rounded">
151
- {getSchemaTypeLabel(selectedSchemaType)}
152
- <button onClick={() => onSchemaTypeChange('all')}>
153
- <XMarkIcon className="h-3 w-3" />
154
- </button>
155
- </span>
156
- )}
76
+ return (
157
77
  <button
158
- onClick={() => {
159
- onSearchChange('');
160
- onTypeChange('all');
161
- onSchemaTypeChange('all');
162
- }}
163
- className="text-xs text-gray-600 hover:text-gray-900 underline"
78
+ key={type}
79
+ onClick={() => onSchemaTypeChange(type)}
80
+ className={`inline-flex items-center gap-1 px-2 py-0.5 rounded text-[11px] font-medium transition-all ${
81
+ selectedSchemaType === type
82
+ ? 'bg-gray-900 text-white'
83
+ : 'bg-gray-50 text-gray-600 border border-gray-200 hover:bg-gray-100'
84
+ }`}
164
85
  >
165
- Clear
86
+ {hasIcon && (
87
+ <img
88
+ src={buildUrl(`/icons/${iconName}.svg`, true)}
89
+ alt={`${type} icon`}
90
+ className={`h-3 w-3 ${selectedSchemaType === type ? 'brightness-0 invert' : 'opacity-60'}`}
91
+ />
92
+ )}
93
+ {getSchemaTypeLabel(type)}
94
+ <span className={`text-[10px] tabular-nums ${selectedSchemaType === type ? 'text-white/60' : 'text-gray-400'}`}>
95
+ {count}
96
+ </span>
166
97
  </button>
167
- </div>
168
- </div>
98
+ );
99
+ })}
100
+ </div>
101
+
102
+ {/* Clear filter */}
103
+ {hasActiveFilters && (
104
+ <button onClick={() => onSchemaTypeChange('all')} className="mt-1.5 text-[11px] text-gray-500 hover:text-gray-700">
105
+ Clear
106
+ </button>
169
107
  )}
170
108
  </div>
171
109
  )}