@eventcatalog/core 0.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 (62) hide show
  1. package/CHANGELOG.md +1 -0
  2. package/README.md +11 -0
  3. package/bin/eventcatalog.js +125 -0
  4. package/components/BreadCrumbs.tsx +50 -0
  5. package/components/ContentView.tsx +127 -0
  6. package/components/Footer.tsx +38 -0
  7. package/components/Grids/EventGrid.tsx +89 -0
  8. package/components/Grids/ServiceGrid.tsx +70 -0
  9. package/components/Header.tsx +59 -0
  10. package/components/Mdx/Admonition.tsx +33 -0
  11. package/components/Mdx/Examples.tsx +77 -0
  12. package/components/Mermaid/index.tsx +47 -0
  13. package/components/NotFound/index.tsx +44 -0
  14. package/components/Sidebars/EventSidebar.tsx +202 -0
  15. package/components/Sidebars/ServiceSidebar.tsx +198 -0
  16. package/components/SyntaxHighlighter.tsx +34 -0
  17. package/hooks/EventCatalog.tsx +35 -0
  18. package/lib/__tests__/assets/events/AddedItemToCart/index.md +19 -0
  19. package/lib/__tests__/assets/events/EmailSent/index.md +15 -0
  20. package/lib/__tests__/assets/events/EventWithSchemaAndExamples/examples/Basic.cs +31 -0
  21. package/lib/__tests__/assets/events/EventWithSchemaAndExamples/examples/Basic.js +1 -0
  22. package/lib/__tests__/assets/events/EventWithSchemaAndExamples/index.md +8 -0
  23. package/lib/__tests__/assets/events/EventWithSchemaAndExamples/schema.json +4 -0
  24. package/lib/__tests__/assets/events/EventWithVersions/index.md +10 -0
  25. package/lib/__tests__/assets/events/EventWithVersions/versioned/0.0.1/index.md +10 -0
  26. package/lib/__tests__/assets/services/Email Platform/index.md +17 -0
  27. package/lib/__tests__/events.spec.ts +294 -0
  28. package/lib/__tests__/file-reader.spec.ts +57 -0
  29. package/lib/__tests__/graphs.spec.ts +62 -0
  30. package/lib/__tests__/services.spec.ts +144 -0
  31. package/lib/events.ts +221 -0
  32. package/lib/file-reader.ts +52 -0
  33. package/lib/graphs.ts +33 -0
  34. package/lib/services.ts +72 -0
  35. package/next-env.d.ts +5 -0
  36. package/next.config.js +3 -0
  37. package/package.json +52 -0
  38. package/pages/_app.tsx +49 -0
  39. package/pages/api/event/[name]/download.js +25 -0
  40. package/pages/events/[name]/logs.tsx +170 -0
  41. package/pages/events/[name]/v/[version].tsx +19 -0
  42. package/pages/events/[name].tsx +139 -0
  43. package/pages/events.tsx +227 -0
  44. package/pages/index.tsx +47 -0
  45. package/pages/overview.tsx +80 -0
  46. package/pages/services/[name].tsx +102 -0
  47. package/pages/services.tsx +123 -0
  48. package/pages/users/[id].tsx +83 -0
  49. package/postcss.config.js +6 -0
  50. package/public/favicon.ico +0 -0
  51. package/public/logo-random.svg +114 -0
  52. package/public/logo.svg +44 -0
  53. package/public/opengraph.png +0 -0
  54. package/scripts/__tests__/assets/eventcatalog.config.js +33 -0
  55. package/scripts/__tests__/generate.spec.ts +39 -0
  56. package/scripts/generate.js +28 -0
  57. package/styles/Home.module.css +116 -0
  58. package/styles/globals.css +48 -0
  59. package/tailwind.config.js +16 -0
  60. package/tsconfig.json +38 -0
  61. package/types/index.ts +7 -0
  62. package/utils/random-bg.ts +13 -0
@@ -0,0 +1,19 @@
1
+ import { getEventByName } from '@/lib/events';
2
+ import EventPage, { EventsPageProps } from '../../[name]';
3
+
4
+ export default function Events(props: EventsPageProps) {
5
+ return <EventPage {...props} />;
6
+ }
7
+
8
+ export async function getServerSideProps({ params }) {
9
+ const { name: eventName, version } = params;
10
+ const { event, markdown } = await getEventByName(eventName, version);
11
+
12
+ return {
13
+ props: {
14
+ event,
15
+ markdown,
16
+ loadedVersion: version,
17
+ },
18
+ };
19
+ }
@@ -0,0 +1,139 @@
1
+ import Head from 'next/head';
2
+ import { MDXRemote } from 'next-mdx-remote';
3
+
4
+ import { Event } from '@eventcatalog/types';
5
+ import { editUrl } from '../../eventcatalog.config';
6
+ import Admonition from '@/components/Mdx/Admonition';
7
+ import Examples from '@/components/Mdx/Examples';
8
+
9
+ import getBackgroundColor from '@/utils/random-bg';
10
+
11
+ import ContentView from '@/components/ContentView';
12
+ import Mermaid from '@/components/Mermaid';
13
+ import EventSideBar from '@/components/Sidebars/EventSidebar';
14
+ import NotFound from '@/components/NotFound';
15
+ import BreadCrumbs from '@/components/BreadCrumbs';
16
+ import SyntaxHighlighter from '@/components/SyntaxHighlighter';
17
+
18
+ import { getAllEvents, getEventByName } from '@/lib/events';
19
+ import { useUrl } from '@/hooks/EventCatalog';
20
+
21
+ import { MarkdownFile } from '@/types/index';
22
+
23
+ export interface EventsPageProps {
24
+ event: Event;
25
+ markdown: MarkdownFile;
26
+ notFound?: boolean;
27
+ loadedVersion?: string;
28
+ }
29
+
30
+ export const getComponents = ({ event, schema, examples }: any) => ({
31
+ code: ({ className, ...props }) => {
32
+ const match = /language-(\w+)/.exec(className || '');
33
+
34
+ return match ? (
35
+ <SyntaxHighlighter language={match[1]} {...props} />
36
+ ) : (
37
+ <code className={className} {...props} />
38
+ );
39
+ },
40
+ Schema: ({ title = 'Event Schema' }: { title: string }) => {
41
+ if (!schema) return null;
42
+
43
+ return (
44
+ <section className="mt-8 xl:mt-10">
45
+ <h2 id="activity-title" className="text-lg font-medium text-gray-900 underline">
46
+ {title}
47
+ </h2>
48
+ <SyntaxHighlighter
49
+ language={schema.language}
50
+ showLineNumbers={false}
51
+ name={`${event.name} Schema (${schema.language})`}
52
+ >
53
+ {schema.snippet}
54
+ </SyntaxHighlighter>
55
+ </section>
56
+ );
57
+ },
58
+ Admonition,
59
+ EventExamples: (props) => <Examples {...props} examples={examples} showLineNumbers />,
60
+ Mermaid: ({ title }: { title: string }) => (
61
+ <div className="mx-auto w-full py-10">
62
+ {title && <h2 className="text-lg font-medium text-gray-900 underline">{title}</h2>}
63
+ <Mermaid source="event" data={event} rootNodeColor={getBackgroundColor(event.name)} />
64
+ </div>
65
+ ),
66
+ });
67
+
68
+ export default function Events(props: EventsPageProps) {
69
+ const { event, markdown, loadedVersion, notFound } = props;
70
+ const { getEditUrl } = useUrl();
71
+
72
+ if (notFound) return <NotFound type="event" name={event.name} editUrl={editUrl} />;
73
+
74
+ const { name, summary, draft, schema, examples, version } = event;
75
+ const { lastModifiedDate } = markdown;
76
+
77
+ const pages = [
78
+ { name: 'Events', href: '/events', current: false },
79
+ { name, href: `/services/${name}`, current: true },
80
+ ];
81
+
82
+ const mdxComponents = getComponents({ event, schema, examples });
83
+
84
+ return (
85
+ <>
86
+ <Head>
87
+ <title>
88
+ {name} v{version}
89
+ </title>
90
+ </Head>
91
+ <ContentView
92
+ // {...props}
93
+ title={name}
94
+ editUrl={getEditUrl(`/events/${name}/index.md`)}
95
+ subtitle={summary}
96
+ draft={draft}
97
+ lastModifiedDate={lastModifiedDate}
98
+ tags={[{ label: `v${version}` }]}
99
+ breadCrumbs={<BreadCrumbs pages={pages} />}
100
+ isOldVersion={loadedVersion !== 'latest'}
101
+ latestVersionUrl={`/events/${name}`}
102
+ version={loadedVersion}
103
+ sidebar={<EventSideBar event={event} loadedVersion={loadedVersion} />}
104
+ >
105
+ <MDXRemote {...markdown.source} components={mdxComponents} />
106
+ </ContentView>
107
+ </>
108
+ );
109
+ }
110
+
111
+ export async function getStaticProps({ params }) {
112
+ try {
113
+ const { event, markdown } = await getEventByName(params.name);
114
+ return {
115
+ props: {
116
+ event,
117
+ markdown,
118
+ loadedVersion: 'latest',
119
+ },
120
+ };
121
+ } catch (error) {
122
+ return {
123
+ props: {
124
+ notFound: true,
125
+ event: { name: params.name },
126
+ },
127
+ };
128
+ }
129
+ }
130
+
131
+ export async function getStaticPaths() {
132
+ const events = getAllEvents();
133
+ const paths = events.map((event) => ({ params: { name: event.name } }));
134
+
135
+ return {
136
+ paths,
137
+ fallback: 'blocking',
138
+ };
139
+ }
@@ -0,0 +1,227 @@
1
+ import { Fragment, useState } from 'react';
2
+ import Head from 'next/head';
3
+ import type { Event, Service } from '@eventcatalog/types';
4
+
5
+ import Link from 'next/link';
6
+
7
+ import { Menu, Transition } from '@headlessui/react';
8
+ import { ChevronDownIcon } from '@heroicons/react/solid';
9
+ import EventGrid from '@/components/Grids/EventGrid';
10
+ import { getAllEvents, getUniqueServicesNamesFromEvents } from '@/lib/events';
11
+
12
+ function classNames(...classes) {
13
+ return classes.filter(Boolean).join(' ');
14
+ }
15
+
16
+ const sortOptions = [
17
+ { name: 'Name', href: '#', current: true },
18
+ { name: 'Version', href: '#', current: false },
19
+ { name: 'Domains', href: '#', current: false },
20
+ ];
21
+
22
+ export interface PageProps {
23
+ events: [Event];
24
+ services: [Service];
25
+ }
26
+
27
+ export default function Page({ events, services }: PageProps) {
28
+ const filters = [
29
+ {
30
+ id: 'services',
31
+ name: 'Services',
32
+ options: services.map((service) => ({
33
+ value: service,
34
+ label: service,
35
+ checked: false,
36
+ })),
37
+ },
38
+ ];
39
+
40
+ const [selectedFilters, setSelectedFilters] = useState({ services: [] });
41
+ const [showMermaidDiagrams, setShowMermaidDiagrams] = useState(false);
42
+
43
+ const handleFilterSelection = (option, type, event) => {
44
+ if (event.target.checked) {
45
+ const newFilters = selectedFilters[type].concat([option.value]);
46
+ setSelectedFilters({ ...selectedFilters, [type]: newFilters });
47
+ } else {
48
+ const newFilters = selectedFilters[type].filter((value) => value !== option.value);
49
+ setSelectedFilters({ ...selectedFilters, [type]: newFilters });
50
+ }
51
+ };
52
+
53
+ let eventsToRender = events;
54
+
55
+ if (selectedFilters.services.length > 0) {
56
+ // @ts-ignore
57
+ eventsToRender = eventsToRender.filter((event) => {
58
+ const { services: serviceFilters } = selectedFilters;
59
+
60
+ const hasConsumersFromFilters = event.consumers.some(
61
+ (consumerId) => serviceFilters.indexOf(consumerId) > -1
62
+ );
63
+ const hasProducersFromFilters = event.producers.some(
64
+ (producerId) => serviceFilters.indexOf(producerId) > -1
65
+ );
66
+
67
+ return hasConsumersFromFilters || hasProducersFromFilters;
68
+ });
69
+ }
70
+
71
+ return (
72
+ <>
73
+ <Head>
74
+ <title>EventCatalog - All Events</title>
75
+ </Head>
76
+ <main className="max-w-7xl mx-auto min-h-screen px-4 md:px-0">
77
+ <div className="relative z-10 flex items-baseline justify-between pt-8 pb-6 border-b border-gray-200">
78
+ <h1 className="text-2xl font-extrabold tracking-tight text-gray-900">
79
+ Events ({events.length})
80
+ </h1>
81
+
82
+ <div className="flex items-center">
83
+ <Menu as="div" className="hidden relative text-left">
84
+ <div>
85
+ <Menu.Button className="group inline-flex justify-center text-sm font-medium text-gray-700 hover:text-gray-900">
86
+ Sort
87
+ <ChevronDownIcon
88
+ className="flex-shrink-0 -mr-1 ml-1 h-5 w-5 text-gray-400 group-hover:text-gray-500"
89
+ aria-hidden="true"
90
+ />
91
+ </Menu.Button>
92
+ </div>
93
+
94
+ <Transition
95
+ as={Fragment}
96
+ enter="transition ease-out duration-100"
97
+ enterFrom="transform opacity-0 scale-95"
98
+ enterTo="transform opacity-100 scale-100"
99
+ leave="transition ease-in duration-75"
100
+ leaveFrom="transform opacity-100 scale-100"
101
+ leaveTo="transform opacity-0 scale-95"
102
+ >
103
+ <Menu.Items className="origin-top-right absolute right-0 mt-2 w-40 rounded-md shadow-2xl bg-white ring-1 ring-black ring-opacity-5 focus:outline-none">
104
+ <div className="py-1">
105
+ {sortOptions.map((option) => (
106
+ <Menu.Item key={option.name}>
107
+ {({ active }) => (
108
+ <a
109
+ href={option.href}
110
+ className={classNames(
111
+ option.current ? 'font-medium text-gray-900' : 'text-gray-500',
112
+ active ? 'bg-gray-100' : '',
113
+ 'block px-4 py-2 text-sm'
114
+ )}
115
+ >
116
+ {option.name}
117
+ </a>
118
+ )}
119
+ </Menu.Item>
120
+ ))}
121
+ </div>
122
+ </Menu.Items>
123
+ </Transition>
124
+ </Menu>
125
+ </div>
126
+ </div>
127
+
128
+ <section className="pt-6 pb-24">
129
+ <div className="grid grid-cols-4 gap-x-8 gap-y-10">
130
+ {/* Filters */}
131
+ <form className="hidden lg:block">
132
+ <span className="text-sm font-bold text-gray-900 mb-4 block">Events</span>
133
+ <ul className=" text-sm text-gray-600 space-y-4 pb-6 border-b border-gray-200 items-stretch">
134
+ {events.map((event) => (
135
+ <li key={event.name}>
136
+ <Link href={`/events/${event.name}`}>
137
+ <a>{event.name}</a>
138
+ </Link>
139
+ </li>
140
+ ))}
141
+ </ul>
142
+
143
+ {filters.map((section: any) => (
144
+ <div key={section.id} className="border-b border-gray-200 py-6">
145
+ <h3 className="-my-3 flow-root">
146
+ <div className="py-3 bg-white w-full flex items-center justify-between text-sm text-gray-400 hover:text-gray-500">
147
+ <span className="font-medium text-gray-900">{section.name}</span>
148
+ </div>
149
+ </h3>
150
+ <div className="pt-6">
151
+ <div className="space-y-4">
152
+ {section.options.map((option, optionIdx) => (
153
+ <div key={option.value} className="flex items-center">
154
+ <input
155
+ id={`filter-${section.id}-${optionIdx}`}
156
+ name={`${section.id}[]`}
157
+ defaultValue={option.value}
158
+ type="checkbox"
159
+ onChange={(event) => handleFilterSelection(option, section.id, event)}
160
+ defaultChecked={option.checked}
161
+ className="h-4 w-4 border-gray-300 rounded text-gray-600 focus:ring-gray-500"
162
+ />
163
+ <label
164
+ htmlFor={`filter-${section.id}-${optionIdx}`}
165
+ className="ml-3 text-sm text-gray-600"
166
+ >
167
+ {option.label}
168
+ </label>
169
+ </div>
170
+ ))}
171
+ </div>
172
+ </div>
173
+ </div>
174
+ ))}
175
+
176
+ <div className="border-b border-gray-200 py-6">
177
+ <h3 className="-my-3 flow-root">
178
+ <div className="py-3 bg-white w-full flex items-center justify-between text-sm text-gray-400 hover:text-gray-500">
179
+ <span className="font-medium text-gray-900">Features</span>
180
+ </div>
181
+ </h3>
182
+ <div className="pt-6">
183
+ <div className="space-y-4">
184
+ <div className="flex items-center">
185
+ <input
186
+ id="show-mermaid"
187
+ type="checkbox"
188
+ onChange={(e) => setShowMermaidDiagrams(e.target.checked)}
189
+ defaultChecked={showMermaidDiagrams}
190
+ className="h-4 w-4 border-gray-300 rounded text-gray-600 focus:ring-gray-500"
191
+ />
192
+ <label htmlFor="show-mermaid" className="ml-3 text-sm text-gray-600">
193
+ Show Mermaid Diagrams
194
+ </label>
195
+ </div>
196
+ </div>
197
+ </div>
198
+ </div>
199
+ </form>
200
+
201
+ <div className="col-span-4 lg:col-span-3">
202
+ <div>
203
+ <h2 className="text-gray-500 text-xs font-medium uppercase tracking-wide">
204
+ Events ({eventsToRender.length})
205
+ </h2>
206
+ <EventGrid events={eventsToRender} showMermaidDiagrams={showMermaidDiagrams} />
207
+ </div>
208
+ </div>
209
+ </div>
210
+ </section>
211
+ </main>
212
+ </>
213
+ );
214
+ }
215
+
216
+ export const getServerSideProps = () => {
217
+ const events = getAllEvents();
218
+ const services = getUniqueServicesNamesFromEvents(events);
219
+
220
+ return {
221
+ props: {
222
+ events,
223
+ // @ts-ignore
224
+ services: [...new Set(services)],
225
+ },
226
+ };
227
+ };
@@ -0,0 +1,47 @@
1
+ /* This example requires Tailwind CSS v2.0+ */
2
+
3
+ import Link from 'next/link';
4
+ import { useConfig } from '@/hooks/EventCatalog';
5
+
6
+ export default function Example() {
7
+ const { title, tagline, logo } = useConfig();
8
+
9
+ const logoToLoad = logo || { alt: 'EventCatalog Logo', src: '/logo.svg' };
10
+
11
+ return (
12
+ <main className="sm:bg-top md:min-h-screen bg-gradient-to-t from-blue-700 to-gray-800">
13
+ <div className="max-w-7xl mx-auto px-4 py-16 text-center sm:px-6 sm:py-24 lg:px-8 lg:py-48">
14
+ <img
15
+ src={logoToLoad.src}
16
+ alt={logoToLoad.alt}
17
+ style={{ height: '85px' }}
18
+ className="mx-auto"
19
+ />
20
+ <h1 className="mt-2 text-4xl font-extrabold text-white tracking-tight sm:text-5xl">
21
+ {title}
22
+ </h1>
23
+ {tagline && <p className="mt-2 text-lg font-medium text-white">{tagline}</p>}
24
+ <div className="mt-5 max-w-md mx-auto sm:flex sm:justify-center md:mt-8">
25
+ <div className="rounded-md shadow">
26
+ <Link href="/events">
27
+ <a className="w-full flex items-center justify-center px-8 py-3 border border-transparent text-base font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 md:py-4 md:text-lg md:px-10">
28
+ Explore Events
29
+ </a>
30
+ </Link>
31
+ </div>
32
+ <div className="mt-3 rounded-md shadow sm:mt-0 sm:ml-3">
33
+ <a
34
+ href="https://eventcatalog.dev"
35
+ target="_blank"
36
+ className="w-full flex items-center justify-center px-8 py-3 border border-transparent text-base font-medium rounded-md text-indigo-600 bg-white hover:bg-gray-50 md:py-4 md:text-lg md:px-10"
37
+ rel="noreferrer"
38
+ >
39
+ Getting Started
40
+ </a>
41
+ </div>
42
+ </div>
43
+ <div className="mt-6 space-x-5" />
44
+ </div>
45
+ </main>
46
+ );
47
+ }
@@ -0,0 +1,80 @@
1
+ import React from 'react';
2
+ import ReactDOMServer from 'react-dom/server';
3
+ import Head from 'next/head';
4
+
5
+ import dynamic from 'next/dynamic';
6
+
7
+ import { getAllEvents, getUniqueServicesNamesFromEvents } from '@/lib/events';
8
+
9
+ const ForceGraph3D = dynamic(
10
+ () => import('react-force-graph-3d').then((module) => module.default),
11
+ { ssr: false }
12
+ );
13
+
14
+ function NodeElement({ node: { id } }: { node: { id: string } }) {
15
+ return <div className={`text-sm text-center p-1 rounded-md `}>{id}</div>;
16
+ }
17
+
18
+ const graph = ({ events, services }) => {
19
+ const eventNodes = events.map(({ name: event }) => ({ id: event, group: 1, type: 'event' }));
20
+ const serviceNodes = services.map((service) => ({ id: service, group: 2, type: 'service' }));
21
+
22
+ // Create all links
23
+ const links = events.reduce((nodes, event) => {
24
+ const { consumers = [], producers = [], name } = event;
25
+ const consumerNodes = consumers.map((consumer) => ({ source: name, target: consumer }));
26
+ const producerNodes = producers.map((producer) => ({ source: producer, target: name }));
27
+ return nodes.concat(consumerNodes).concat(producerNodes);
28
+ }, []);
29
+
30
+ const data = { nodes: eventNodes.concat(serviceNodes), links };
31
+
32
+ if (typeof window === 'undefined') {
33
+ return null;
34
+ }
35
+
36
+ // @ts-ignore
37
+ const extraRenderers = [new window.THREE.CSS2DRenderer()];
38
+ return (
39
+ <div className="min-h-screen ">
40
+ <Head>
41
+ <title>EventCatalog - 3D Node Graph</title>
42
+ </Head>
43
+ <ForceGraph3D
44
+ extraRenderers={extraRenderers}
45
+ graphData={data}
46
+ nodeAutoColorBy="group"
47
+ nodeRelSize={9}
48
+ nodeThreeObject={(node) => {
49
+ const nodeEl = document.createElement('div');
50
+ nodeEl.innerHTML = ReactDOMServer.renderToString(<NodeElement node={node} />);
51
+ node.height = '100px';
52
+ nodeEl.style.color = node.color;
53
+ // @ts-ignore
54
+ return new THREE.CSS2DObject(nodeEl);
55
+ }}
56
+ nodeThreeObjectExtend
57
+ enableNodeDrag={false}
58
+ nodeOpacity={0.2}
59
+ linkDirectionalParticles={2}
60
+ linkDirectionalParticleWidth={2}
61
+ linkDirectionalParticleColor={() => 'rgba(236, 72, 153, 1)'}
62
+ />
63
+ </div>
64
+ );
65
+ };
66
+
67
+ export default graph;
68
+
69
+ export const getServerSideProps = () => {
70
+ const events = getAllEvents();
71
+ const services = getUniqueServicesNamesFromEvents(events);
72
+
73
+ return {
74
+ props: {
75
+ events,
76
+ // @ts-ignore
77
+ services: [...new Set(services)],
78
+ },
79
+ };
80
+ };
@@ -0,0 +1,102 @@
1
+ import Head from 'next/head';
2
+ import { MDXRemote } from 'next-mdx-remote';
3
+ import { Service } from '@eventcatalog/types';
4
+ import { editUrl } from '../../eventcatalog.config.js';
5
+ import ContentView from '@/components/ContentView';
6
+ import { getAllServices, getServiceByName } from '@/lib/services';
7
+
8
+ import Admonition from '@/components/Mdx/Admonition';
9
+ import Mermaid from '@/components/Mermaid';
10
+ import ServiceSidebar from '@/components/Sidebars/ServiceSidebar';
11
+ import BreadCrumbs from '@/components/BreadCrumbs';
12
+ import NotFound from '@/components/NotFound';
13
+ import getBackgroundColor from '@/utils/random-bg';
14
+ import { useUrl } from '@/hooks/EventCatalog';
15
+
16
+ import { MarkdownFile } from '@/types/index';
17
+
18
+ interface ServicesPageProps {
19
+ service: Service;
20
+ markdown: MarkdownFile;
21
+ notFound?: boolean;
22
+ }
23
+
24
+ function MermaidComponent({ title, service }: { title: string; service: Service }) {
25
+ return (
26
+ <div className="mx-auto w-full py-10">
27
+ {title && <h2 className="text-lg font-medium text-gray-900 underline">{title}</h2>}
28
+ <Mermaid source="service" data={service} rootNodeColor={getBackgroundColor(service.name)} />
29
+ </div>
30
+ );
31
+ }
32
+
33
+ const getComponents = (service) => ({
34
+ Admonition,
35
+ Mermaid: ({ title }: { title: string }) => <MermaidComponent service={service} title={title} />,
36
+ });
37
+
38
+ export default function Services(props: ServicesPageProps) {
39
+ const { service, markdown, notFound } = props;
40
+ const { getEditUrl } = useUrl();
41
+
42
+ if (notFound) return <NotFound type="service" name={service.name} editUrl={editUrl} />;
43
+
44
+ const { name, summary, draft } = service;
45
+ const { lastModifiedDate } = markdown;
46
+
47
+ const mdxComponents = getComponents(service);
48
+
49
+ const pages = [
50
+ { name: 'Services', href: '/services', current: false },
51
+ { name, href: `/services/${name}`, current: true },
52
+ ];
53
+
54
+ return (
55
+ <>
56
+ <Head>
57
+ <title>{name}</title>
58
+ </Head>
59
+ <ContentView
60
+ title={name}
61
+ editUrl={getEditUrl(`/services/${name}/index.md`)}
62
+ subtitle={summary}
63
+ draft={draft}
64
+ lastModifiedDate={lastModifiedDate}
65
+ breadCrumbs={<BreadCrumbs pages={pages} homePath="/services" />}
66
+ sidebar={<ServiceSidebar service={service} />}
67
+ >
68
+ {/* @ts-ignore */}
69
+ <MDXRemote {...markdown.source} components={mdxComponents} />
70
+ </ContentView>
71
+ </>
72
+ );
73
+ }
74
+
75
+ export async function getStaticProps({ params }) {
76
+ try {
77
+ const { service, markdown } = await getServiceByName(params.name);
78
+
79
+ return {
80
+ props: {
81
+ service,
82
+ markdown,
83
+ },
84
+ };
85
+ } catch (error) {
86
+ return {
87
+ props: {
88
+ notFound: true,
89
+ service: { name: params.name },
90
+ },
91
+ };
92
+ }
93
+ }
94
+
95
+ export async function getStaticPaths() {
96
+ const services = getAllServices();
97
+ const paths = services.map((service) => ({ params: { name: service.name } }));
98
+ return {
99
+ paths,
100
+ fallback: 'blocking',
101
+ };
102
+ }