@motiadev/workbench 0.4.1-beta.92 → 0.4.1-beta.93-636277

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.
@@ -0,0 +1,11 @@
1
+ import { ApiEndpoint } from '@/types/endpoint';
2
+ import { FC } from 'react';
3
+ import 'react18-json-view/src/dark.css';
4
+ import 'react18-json-view/src/style.css';
5
+ type Props = {
6
+ endpoint: ApiEndpoint;
7
+ onChange?: (body: string) => void;
8
+ onValidate?: (isValid: boolean) => void;
9
+ };
10
+ export declare const EndpointBodyPanel: FC<Props>;
11
+ export {};
@@ -0,0 +1,20 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useJsonSchemaToJson } from './hooks/use-json-schema-to-json';
3
+ import { Panel } from '@motiadev/ui';
4
+ import { JsonEditor } from './json-editor';
5
+ import ReactJson from 'react18-json-view';
6
+ import 'react18-json-view/src/dark.css';
7
+ import 'react18-json-view/src/style.css';
8
+ import { convertJsonSchemaToJson } from './hooks/utils';
9
+ export const EndpointBodyPanel = ({ endpoint, onChange, onValidate }) => {
10
+ const shouldHaveBody = ['post', 'put', 'patch'].includes(endpoint.method.toLowerCase());
11
+ const { body, setBody } = useJsonSchemaToJson(endpoint.bodySchema);
12
+ const handleBodyChange = (body) => {
13
+ setBody(body);
14
+ onChange?.(body);
15
+ };
16
+ if (!shouldHaveBody) {
17
+ return null;
18
+ }
19
+ return (_jsx(Panel, { title: "Body", size: "sm", contentClassName: "p-0", "data-testid": "endpoint-body-panel", children: onChange ? (_jsx(JsonEditor, { value: body, schema: endpoint.bodySchema, onChange: handleBodyChange, onValidate: onValidate })) : (_jsx(ReactJson, { src: convertJsonSchemaToJson(endpoint.bodySchema ?? {}), theme: "default", enableClipboard: false })) }));
20
+ };
@@ -2,7 +2,6 @@ import { ApiEndpoint } from '@/types/endpoint';
2
2
  import { FC } from 'react';
3
3
  type Props = {
4
4
  endpoint: ApiEndpoint;
5
- onClose: () => void;
6
5
  };
7
6
  export declare const EndpointCall: FC<Props>;
8
7
  export {};
@@ -1,38 +1,27 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Sidebar } from '@/components/sidebar/sidebar';
3
- import { Button, Input, Panel } from '@motiadev/ui';
4
- import { Loader2, Play, X } from 'lucide-react';
2
+ import { Button } from '@motiadev/ui';
3
+ import { Loader2, Play } from 'lucide-react';
5
4
  import { useEffect, useMemo, useState } from 'react';
6
- import { EndpointBadge } from './endpoint-badge';
5
+ import { EndpointBodyPanel } from './endpoint-body-panel';
6
+ import { EndpointPathParamsPanel } from './endpoint-path-params-panel';
7
+ import { EndpointQueryParamsPanel } from './endpoint-query-params-panel';
7
8
  import { EndpointResponse } from './endpoint-response';
8
- import { EndpointResponseSchema } from './endpoint-response-schema';
9
- import { useJsonSchemaToJson } from './hooks/use-json-schema-to-json';
10
- import { usePathParams } from './hooks/use-path-params';
11
- import { JsonEditor } from './json-editor';
12
- export const EndpointCall = ({ endpoint, onClose }) => {
9
+ export const EndpointCall = ({ endpoint }) => {
13
10
  const shouldHaveBody = ['post', 'put', 'patch'].includes(endpoint.method.toLowerCase());
14
11
  const [isRequestLoading, setIsRequestLoading] = useState(false);
15
12
  const [responseCode, setResponseCode] = useState(undefined);
16
13
  const [responseBody, setResponseBody] = useState(undefined);
17
14
  const [executionTime, setExecutionTime] = useState(undefined);
18
- const { body, setBody } = useJsonSchemaToJson(endpoint.bodySchema);
15
+ const [body, setBody] = useState(undefined);
19
16
  const [isBodyValid, setIsBodyValid] = useState(true);
20
- const pathParams = usePathParams(endpoint.path);
21
- const [pathParamsValues, setPathParamsValues] = useState(pathParams?.reduce((acc, param) => ({ ...acc, [param]: '' }), {}));
22
- const [queryParamsValues, setQueryParamsValues] = useState(endpoint.queryParams?.reduce((acc, param) => ({ ...acc, [param.name]: '' }), {}) ?? {});
17
+ const [pathParamsValues, setPathParamsValues] = useState({});
18
+ const [queryParamsValues, setQueryParamsValues] = useState({});
23
19
  const isPlayEnabled = useMemo(() => {
24
- if (!pathParams)
25
- return true;
26
20
  if (shouldHaveBody && !isBodyValid)
27
21
  return false;
28
- return pathParams?.every((param) => pathParamsValues[param]);
29
- }, [pathParams, pathParamsValues, shouldHaveBody, isBodyValid]);
30
- const onPathParamChange = (param, value) => {
31
- setPathParamsValues((prev) => ({ ...prev, [param]: value }));
32
- };
33
- const onQueryParamChange = (param, value) => {
34
- setQueryParamsValues((prev) => ({ ...prev, [param]: value }));
35
- };
22
+ return (Object.values(pathParamsValues).every((value) => value) &&
23
+ Object.values(queryParamsValues).every((value) => value));
24
+ }, [pathParamsValues, shouldHaveBody, isBodyValid, queryParamsValues]);
36
25
  useEffect(() => {
37
26
  if (endpoint.id) {
38
27
  setResponseCode(undefined);
@@ -45,8 +34,8 @@ export const EndpointCall = ({ endpoint, onClose }) => {
45
34
  setIsRequestLoading(true);
46
35
  const startTime = Date.now();
47
36
  const path = new URL(window.location.origin +
48
- pathParams.reduce((acc, param) => {
49
- return acc.replace(`:${param}`, pathParamsValues[param]);
37
+ Object.entries(pathParamsValues).reduce((acc, [param, value]) => {
38
+ return acc.replace(`:${param}`, value);
50
39
  }, endpoint.path));
51
40
  for (const [key, value] of Object.entries(queryParamsValues)) {
52
41
  path.searchParams.set(key, value);
@@ -64,13 +53,5 @@ export const EndpointCall = ({ endpoint, onClose }) => {
64
53
  setExecutionTime(executionTime);
65
54
  setIsRequestLoading(false);
66
55
  };
67
- return (_jsxs(Sidebar, { initialWidth: 600, title: _jsxs("div", { className: "flex flex-row gap-2 items-center", children: [_jsx(EndpointBadge, { variant: endpoint.method, children: endpoint.method.toUpperCase() }), _jsx("span", { className: "text-md font-bold", children: endpoint.path })] }), onClose: onClose, actions: [
68
- {
69
- icon: _jsx(X, { className: "cursor-pointer w-4 h-4", onClick: onClose }),
70
- onClick: onClose,
71
- },
72
- ], children: [endpoint.description && (_jsx("div", { className: "rounded-lg border p-4 font-medium text-muted-foreground", children: endpoint.description })), !!pathParams.length && (_jsx(Panel, { title: "Path params", size: "sm", children: _jsx("table", { children: pathParams.map((param) => (_jsxs("tr", { children: [_jsx("td", { className: "flex flex-col font-bold leading-[36px]", children: param }), _jsx("td", { className: "w-2/3 pl-4", children: _jsx(Input, { className: "w-full", value: pathParamsValues[param], onChange: (e) => onPathParamChange(param, e.target.value) }) })] }, param))) }) })), !!endpoint.queryParams?.length && (_jsx(Panel, { title: "Query params", size: "sm", children: _jsx("table", { children: endpoint.queryParams.map((param) => (_jsxs("tr", { children: [_jsxs("td", { className: "flex flex-col justify-start", children: [_jsx("span", { className: "font-bold", children: param.name }), _jsx("span", { className: "text-md text-muted-foreground", children: param.description })] }), _jsx("td", { className: "w-2/3 pl-4 align-top", children: _jsx(Input, { className: "w-full", value: queryParamsValues[param.name], onChange: (e) => onQueryParamChange(param.name, e.target.value) }) })] }, param.name))) }) })), shouldHaveBody && (_jsx(Panel, { title: "Body", size: "sm", contentClassName: "p-0", "data-testid": "endpoint-body-panel", children: _jsx(JsonEditor, { value: body, schema: endpoint.bodySchema, onChange: setBody, onValidate: setIsBodyValid }) })), _jsxs(Button, { className: "w-fit", onClick: handleRequest, variant: "accent", "data-testid": "endpoint-play-button", disabled: isRequestLoading || !isPlayEnabled, children: [isRequestLoading ? _jsx(Loader2, { className: "animate-spin" }) : _jsx(Play, {}), " Play"] }), _jsx(EndpointResponse, { responseCode: responseCode, responseBody: responseBody, executionTime: executionTime }), _jsx(EndpointResponseSchema, { items: Object.entries(endpoint?.responseSchema ?? {}).map(([status, schema]) => ({
73
- responseCode: status,
74
- bodySchema: schema,
75
- })) })] }));
56
+ return (_jsxs("div", { className: "space-y-3", children: [_jsx(EndpointPathParamsPanel, { endpoint: endpoint, onChange: setPathParamsValues }), _jsx(EndpointQueryParamsPanel, { endpoint: endpoint, onChange: setQueryParamsValues }), _jsx(EndpointBodyPanel, { endpoint: endpoint, onChange: setBody, onValidate: setIsBodyValid }), _jsxs(Button, { className: "w-fit", onClick: handleRequest, variant: "accent", "data-testid": "endpoint-play-button", disabled: isRequestLoading || !isPlayEnabled, children: [isRequestLoading ? _jsx(Loader2, { className: "animate-spin" }) : _jsx(Play, {}), " Play"] }), _jsx(EndpointResponse, { responseCode: responseCode, responseBody: responseBody, executionTime: executionTime })] }));
76
57
  };
@@ -0,0 +1,7 @@
1
+ import { FC } from 'react';
2
+ import { ApiEndpoint } from '@/types/endpoint';
3
+ type Props = {
4
+ endpoint: ApiEndpoint;
5
+ };
6
+ export declare const EndpointDescription: FC<Props>;
7
+ export {};
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { EndpointResponseSchema } from './endpoint-response-schema';
3
+ import { EndpointPathParamsPanel } from './endpoint-path-params-panel';
4
+ import { EndpointQueryParamsPanel } from './endpoint-query-params-panel';
5
+ import { EndpointBodyPanel } from './endpoint-body-panel';
6
+ export const EndpointDescription = ({ endpoint }) => {
7
+ return (_jsxs("div", { className: "space-y-3", children: [_jsx(EndpointPathParamsPanel, { endpoint: endpoint }), _jsx(EndpointQueryParamsPanel, { endpoint: endpoint }), _jsx(EndpointBodyPanel, { endpoint: endpoint }), _jsx(EndpointResponseSchema, { items: Object.entries(endpoint?.responseSchema ?? {}).map(([status, schema]) => ({
8
+ responseCode: status,
9
+ bodySchema: schema,
10
+ })) })] }));
11
+ };
@@ -0,0 +1,8 @@
1
+ import { FC } from 'react';
2
+ import { ApiEndpoint } from '@/types/endpoint';
3
+ type Props = {
4
+ endpoint: ApiEndpoint;
5
+ onChange?: (pathParamsValues: Record<string, string>) => void;
6
+ };
7
+ export declare const EndpointPathParamsPanel: FC<Props>;
8
+ export {};
@@ -0,0 +1,28 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Input, Panel } from '@motiadev/ui';
3
+ import { Fragment, useMemo, useState } from 'react';
4
+ import { usePathParams } from './hooks/use-path-params';
5
+ export const EndpointPathParamsPanel = ({ endpoint, onChange }) => {
6
+ const pathParams = usePathParams(endpoint.path);
7
+ const [pathParamsValues, setPathParamsValues] = useState(pathParams?.reduce((acc, param) => ({ ...acc, [param]: '' }), {}));
8
+ const onPathParamChange = (param, value) => {
9
+ const newPathParamsValues = { ...pathParamsValues, [param]: value };
10
+ setPathParamsValues(newPathParamsValues);
11
+ onChange?.(newPathParamsValues);
12
+ };
13
+ const subtitle = useMemo(() => {
14
+ if (onChange) {
15
+ return Object.entries(pathParamsValues).reduce((acc, [param, value]) => {
16
+ if (!value) {
17
+ return acc;
18
+ }
19
+ return acc.replace(`:${param}`, value);
20
+ }, endpoint.path);
21
+ }
22
+ return endpoint.path;
23
+ }, [pathParamsValues, endpoint.path, onChange]);
24
+ if (!pathParams.length) {
25
+ return null;
26
+ }
27
+ return (_jsx(Panel, { title: "Path params", subtitle: subtitle, size: "sm", variant: "outlined", children: _jsx("div", { className: "grid gap-3", style: { gridTemplateColumns: '1fr 2fr' }, children: pathParams.map((param) => (_jsxs(Fragment, { children: [_jsx("div", { className: "font-bold leading-[36px] flex text-xs", children: param }), _jsx("div", { className: "flex items-center text-xs", children: onChange ? (_jsx(Input, { className: "w-full text-xs", placeholder: `:${param}`, value: pathParamsValues[param], onChange: (e) => onPathParamChange(param, e.target.value) })) : (_jsx("span", { className: "text-xs", children: `:${param}` })) })] }, param))) }) }));
28
+ };
@@ -0,0 +1,8 @@
1
+ import { ApiEndpoint } from '@/types/endpoint';
2
+ import { FC } from 'react';
3
+ type Props = {
4
+ endpoint: ApiEndpoint;
5
+ onChange?: (queryParamsValues: Record<string, string>) => void;
6
+ };
7
+ export declare const EndpointQueryParamsPanel: FC<Props>;
8
+ export {};
@@ -0,0 +1,16 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Input } from '@motiadev/ui';
3
+ import { Panel } from '@motiadev/ui';
4
+ import { Fragment, useState } from 'react';
5
+ export const EndpointQueryParamsPanel = ({ endpoint, onChange }) => {
6
+ const [queryParamsValues, setQueryParamsValues] = useState(endpoint.queryParams?.reduce((acc, param) => ({ ...acc, [param.name]: '' }), {}) ?? {});
7
+ const onQueryParamChange = (param, value) => {
8
+ const newQueryParamsValues = { ...queryParamsValues, [param]: value };
9
+ setQueryParamsValues(newQueryParamsValues);
10
+ onChange?.(newQueryParamsValues);
11
+ };
12
+ if (!endpoint.queryParams?.length) {
13
+ return null;
14
+ }
15
+ return (_jsx(Panel, { title: "Query params", size: "sm", variant: "outlined", children: _jsx("div", { className: "grid gap-3", style: { gridTemplateColumns: '1fr 2fr', gridTemplateRows: '1fr 1fr' }, children: endpoint.queryParams.map((param) => (_jsxs(Fragment, { children: [_jsx("div", { className: "font-bold leading-[36px] flex text-xs", children: param.name }), _jsx("div", { className: "flex items-center text-xs ", children: onChange ? (_jsx(Input, { className: "text-xs", placeholder: param.description, value: queryParamsValues[param.name], onChange: (e) => onQueryParamChange(param.name, e.target.value) })) : (_jsx("span", { children: param.description })) })] }, param.name))) }) }));
16
+ };
@@ -13,5 +13,5 @@ export const EndpointResponseSchema = ({ items }) => {
13
13
  if (items.length === 0) {
14
14
  return null;
15
15
  }
16
- return (_jsx("div", { className: "flex flex-col rounded-lg border", "data-testid": "endpoint-response-container", children: _jsxs(Tabs, { defaultValue: items[0].responseCode, children: [_jsx("div", { className: "flex items-center justify-between bg-card", children: _jsx(TabsList, { className: "bg-transparent p-0", children: items.map((item) => (_jsx(TabsTrigger, { value: item.responseCode, className: "text-xs font-bold cursor-pointer", children: item.responseCode }, item.responseCode))) }) }), items.map((props) => (_jsx(EndpointResponseSchemaItem, { ...props }, props.responseCode)))] }) }));
16
+ return (_jsx("div", { className: "flex flex-col rounded-lg border", children: _jsxs(Tabs, { defaultValue: items[0].responseCode, children: [_jsx("div", { className: "flex items-center justify-between bg-card", children: _jsx(TabsList, { className: "bg-transparent p-0", children: items.map((item) => (_jsx(TabsTrigger, { value: item.responseCode, className: "text-xs font-bold cursor-pointer", children: item.responseCode }, item.responseCode))) }) }), items.map((props) => (_jsx(EndpointResponseSchemaItem, { ...props }, props.responseCode)))] }) }));
17
17
  };
@@ -35,18 +35,16 @@ export const EndpointResponse = ({ responseCode, executionTime, responseBody })
35
35
  if (!responseBody || !responseCode) {
36
36
  return null;
37
37
  }
38
- return (_jsx(Panel, { tabs: isStreamed
39
- ? {
40
- tabs: [
41
- {
42
- label: 'Streamed Response',
43
- content: (_jsx(ReactJson, { src: data, dark: theme === 'dark', style: { backgroundColor: 'transparent' } })),
44
- },
45
- {
46
- label: 'Original',
47
- content: (_jsx(ReactJson, { src: originalData, dark: theme === 'dark', style: { backgroundColor: 'transparent' } })),
48
- },
49
- ],
50
- }
38
+ return (_jsx(Panel, { "data-testid": 'endpoint-response-container', tabs: isStreamed
39
+ ? [
40
+ {
41
+ label: 'Streamed Response',
42
+ content: (_jsx(ReactJson, { src: data, dark: theme === 'dark', style: { backgroundColor: 'transparent' } })),
43
+ },
44
+ {
45
+ label: 'Original',
46
+ content: (_jsx(ReactJson, { src: originalData, dark: theme === 'dark', style: { backgroundColor: 'transparent' } })),
47
+ },
48
+ ]
51
49
  : undefined, title: _jsxs("div", { className: "flex flex-row justify-between items-center flex-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [isError && _jsx(XCircle, { className: "text-red-500 w-4 h-4" }), _jsxs("span", { className: "font-bold text-xs", children: [responseCode, " - ", statusMessage] })] }), !!executionTime && (_jsxs("span", { className: "text-xs text-muted-foreground justify-self-end", children: ["Execution time: ", _jsxs("span", { className: "font-bold", children: [executionTime, "ms"] })] }))] }), subtitle: isStreamed && (_jsxs("span", { className: "col-span-2 flex flex-row items-center font-medium text-card-foreground text-xs mt-2", children: [_jsxs("span", { className: "inline-block w-2 h-2 rounded-full bg-green-500 mr-2 relative", children: [_jsx("span", { className: "absolute inset-0 rounded-full bg-green-500 animate-[ping_1.5s_ease-in-out_infinite]" }), _jsx("span", { className: "absolute inset-0 rounded-full bg-green-500" })] }), "Object is being streamed, this is not the actual response from the API Endpoint"] })), children: !isStreamed && (_jsx(ReactJson, { src: data, dark: theme === 'dark', style: { backgroundColor: 'transparent' } })) }));
52
50
  };
@@ -0,0 +1,8 @@
1
+ import { ApiEndpoint } from '@/types/endpoint';
2
+ import { FC } from 'react';
3
+ type Props = {
4
+ endpoint: ApiEndpoint;
5
+ onClose: () => void;
6
+ };
7
+ export declare const EndpointSidePanel: FC<Props>;
8
+ export {};
@@ -0,0 +1,25 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Sidebar } from '@/components/sidebar/sidebar';
3
+ import { X } from 'lucide-react';
4
+ import { EndpointBadge } from './endpoint-badge';
5
+ import { EndpointCall } from './endpoint-call';
6
+ import { EndpointDescription } from './endpoint-description';
7
+ export const EndpointSidePanel = ({ endpoint, onClose }) => {
8
+ return (_jsx(Sidebar, { initialWidth: 600, subtitle: endpoint.description, title: _jsxs("div", { className: "flex flex-row gap-2 items-center", children: [_jsx(EndpointBadge, { variant: endpoint.method, children: endpoint.method.toUpperCase() }), _jsx("span", { className: "text-md font-bold", children: endpoint.path })] }), onClose: onClose, tabs: [
9
+ {
10
+ label: 'Details',
11
+ content: _jsx(EndpointDescription, { endpoint: endpoint }),
12
+ 'data-testid': 'endpoint-details-tab',
13
+ },
14
+ {
15
+ label: 'Call',
16
+ content: _jsx(EndpointCall, { endpoint: endpoint }),
17
+ 'data-testid': 'endpoint-call-tab',
18
+ },
19
+ ], actions: [
20
+ {
21
+ icon: _jsx(X, { className: "cursor-pointer w-4 h-4", onClick: onClose }),
22
+ onClick: onClose,
23
+ },
24
+ ] }));
25
+ };
@@ -3,12 +3,12 @@ import { cn } from '@/lib/utils';
3
3
  import { useGlobalStore } from '@/stores/use-global-store';
4
4
  import { useMemo } from 'react';
5
5
  import { EndpointBadge } from './endpoint-badge';
6
- import { EndpointCall } from './endpoint-call';
6
+ import { EndpointSidePanel } from './endpoint-side-panel';
7
7
  import { useGetEndpoints } from './hooks/use-get-endpoints';
8
8
  export const EndpointsPage = () => {
9
9
  const endpoints = useGetEndpoints();
10
10
  const selectedEndpointId = useGlobalStore((state) => state.selectedEndpointId);
11
11
  const selectEndpointId = useGlobalStore((state) => state.selectEndpointId);
12
12
  const selectedEndpoint = useMemo(() => selectedEndpointId && endpoints.find((endpoint) => endpoint.id === selectedEndpointId), [endpoints, selectedEndpointId]);
13
- return (_jsxs("div", { className: "flex flex-row w-full h-full", children: [_jsx("div", { className: "flex flex-col flex-1 overflow-y-auto", children: endpoints.map((endpoint) => (_jsx("div", { "data-testid": `endpoint-${endpoint.method}-${endpoint.path}`, className: cn(selectedEndpoint === endpoint && 'bg-muted-foreground/10', 'cursor-pointer select-none'), onClick: () => selectEndpointId(endpoint.id), children: _jsxs("div", { className: "flex flex-row gap-2 items-center hover:bg-muted-foreground/10 p-2", children: [_jsx(EndpointBadge, { variant: endpoint.method, children: endpoint.method.toUpperCase() }), _jsx("span", { className: "text-md font-mono font-bold whitespace-nowrap", children: endpoint.path }), _jsx("span", { className: "text-md text-muted-foreground truncate", children: endpoint.description })] }) }, `${endpoint.method} ${endpoint.path}`))) }), selectedEndpoint && _jsx(EndpointCall, { endpoint: selectedEndpoint, onClose: () => selectEndpointId(undefined) })] }));
13
+ return (_jsxs("div", { className: "flex flex-row w-full h-full", children: [_jsx("div", { className: "flex flex-col flex-1 overflow-y-auto", children: endpoints.map((endpoint) => (_jsx("div", { "data-testid": `endpoint-${endpoint.method}-${endpoint.path}`, className: cn(selectedEndpoint === endpoint && 'bg-muted-foreground/10', 'cursor-pointer select-none'), onClick: () => selectEndpointId(endpoint.id), children: _jsxs("div", { className: "flex flex-row gap-2 items-center hover:bg-muted-foreground/10 p-2", children: [_jsx(EndpointBadge, { variant: endpoint.method, children: endpoint.method.toUpperCase() }), _jsx("span", { className: "text-md font-mono font-bold whitespace-nowrap", children: endpoint.path }), _jsx("span", { className: "text-md text-muted-foreground truncate", children: endpoint.description })] }) }, `${endpoint.method} ${endpoint.path}`))) }), selectedEndpoint && (_jsx(EndpointSidePanel, { endpoint: selectedEndpoint, onClose: () => selectEndpointId(undefined) }))] }));
14
14
  };
@@ -14,7 +14,7 @@ export const convertJsonSchemaToJson = (schema) => {
14
14
  case 'array':
15
15
  return [convertJsonSchemaToJson(schema.items)];
16
16
  case 'string':
17
- return schema.description ?? 'string';
17
+ return schema.enum?.[0] ?? schema.description ?? 'string';
18
18
  case 'number':
19
19
  return schema.description ?? 0;
20
20
  case 'integer':
@@ -3,7 +3,7 @@ type JsonEditorProps = {
3
3
  value: string;
4
4
  schema: Record<string, unknown> | undefined;
5
5
  onChange: (value: string) => void;
6
- onValidate: (isValid: boolean) => void;
6
+ onValidate?: (isValid: boolean) => void;
7
7
  };
8
8
  export declare const JsonEditor: FC<JsonEditorProps>;
9
9
  export {};
@@ -22,8 +22,8 @@ export const JsonEditor = ({ value, schema, onChange, onValidate }) => {
22
22
  }, [monaco, schema]);
23
23
  return (_jsx(Editor, { "data-testid": "json-editor", height: "200px", language: "json", value: value, theme: editorTheme, onChange: (value) => {
24
24
  if (!value) {
25
- onValidate(false);
25
+ onValidate?.(false);
26
26
  }
27
27
  onChange(value ?? '');
28
- }, onValidate: (markers) => onValidate(markers.length === 0), options: { lineNumbers: 'off', minimap: { enabled: false } } }));
28
+ }, onValidate: (markers) => onValidate?.(markers.length === 0), options: { lineNumbers: 'off', minimap: { enabled: false } } }));
29
29
  };
@@ -6,7 +6,7 @@ import { useLogsStore } from '@/stores/use-logs-store';
6
6
  import { useMemo, useState } from 'react';
7
7
  import { LogDetail } from './log-detail';
8
8
  import { LogLevelDot } from './log-level-dot';
9
- import { Button, Input } from '@motiadev/ui';
9
+ import { Button, cn, Input } from '@motiadev/ui';
10
10
  import { CircleX, Trash } from 'lucide-react';
11
11
  export const LogsPage = () => {
12
12
  const logs = useLogsStore((state) => state.logs);
@@ -22,5 +22,8 @@ export const LogsPage = () => {
22
22
  log.step.toLowerCase().includes(search.toLowerCase()));
23
23
  });
24
24
  }, [logs, search]);
25
- return (_jsxs("div", { className: "h-full flex flex-row", children: [_jsxs("div", { className: "flex-1 overflow-y-auto overflow-x-auto", children: [_jsxs("div", { className: "flex p-2 border-b gap-4", children: [_jsxs("div", { className: "flex-1 relative", children: [_jsx(Input, { variant: "shade", value: search, onChange: (e) => setSearch(e.target.value), className: "pr-10 font-medium" }), _jsx(CircleX, { className: "cursor-pointer absolute right-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground/50 hover:text-muted-foreground", onClick: () => setSearch('') })] }), _jsxs(Button, { variant: "outline", onClick: resetLogs, children: [_jsx(Trash, {}), " Clear"] })] }), _jsx(Table, { children: _jsx(TableBody, { className: "font-mono font-medium", children: filteredLogs.map((log, index) => (_jsxs(TableRow, { className: "cursor-pointer border-0", onClick: () => selectLogId(log.id), children: [_jsxs(TableCell, { "data-testid": `time-${index}`, className: "whitespace-nowrap flex items-center gap-2 text-muted-foreground", children: [_jsx(LogLevelDot, { level: log.level }), formatTimestamp(log.time)] }), _jsx(TableCell, { "data-testid": `trace-${log.traceId}`, className: "whitespace-nowrap cursor-pointer hover:text-primary text-muted-foreground", onClick: () => setSearch(log.traceId), children: log.traceId }), _jsx(TableCell, { "data-testid": `step-${index}`, "aria-label": log.step, className: "whitespace-nowrap", children: log.step }), _jsx(TableCell, { "data-testid": `msg-${index}`, "aria-label": log.msg, className: "whitespace-nowrap max-w-[500px] truncate w-full", children: log.msg })] }, index))) }) })] }), _jsx(LogDetail, { log: selectedLog, onClose: () => selectLogId(undefined) })] }));
25
+ return (_jsxs("div", { className: "h-full flex flex-row", children: [_jsxs("div", { className: "flex-1 overflow-y-auto overflow-x-auto", children: [_jsxs("div", { className: "flex p-2 border-b gap-4", children: [_jsxs("div", { className: "flex-1 relative", children: [_jsx(Input, { variant: "shade", value: search, onChange: (e) => setSearch(e.target.value), className: "pr-10 font-medium" }), _jsx(CircleX, { className: "cursor-pointer absolute right-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground/50 hover:text-muted-foreground", onClick: () => setSearch('') })] }), _jsxs(Button, { variant: "outline", onClick: resetLogs, children: [_jsx(Trash, {}), " Clear"] })] }), _jsx(Table, { children: _jsx(TableBody, { className: "font-mono font-medium", children: filteredLogs.map((log, index) => (_jsxs(TableRow, { className: cn('font-mono font-semibold cursor-pointer border-0', {
26
+ 'bg-muted-foreground/10 hover:bg-muted-foreground/20': selectedLogId === log.id,
27
+ 'hover:bg-muted-foreground/10': selectedLogId !== log.id,
28
+ }), onClick: () => selectLogId(log.id), children: [_jsxs(TableCell, { "data-testid": `time-${index}`, className: "whitespace-nowrap flex items-center gap-2 text-muted-foreground", children: [_jsx(LogLevelDot, { level: log.level }), formatTimestamp(log.time)] }), _jsx(TableCell, { "data-testid": `trace-${log.traceId}`, className: "whitespace-nowrap cursor-pointer hover:text-primary text-muted-foreground", onClick: () => setSearch(log.traceId), children: log.traceId }), _jsx(TableCell, { "data-testid": `step-${index}`, "aria-label": log.step, className: "whitespace-nowrap", children: log.step }), _jsx(TableCell, { "data-testid": `msg-${index}`, "aria-label": log.msg, className: "whitespace-nowrap max-w-[500px] truncate w-full", children: log.msg })] }, index))) }) })] }), _jsx(LogDetail, { log: selectedLog, onClose: () => selectLogId(undefined) })] }));
26
29
  };
@@ -12,7 +12,7 @@ export const StatesPage = () => {
12
12
  const selectedItem = useMemo(() => (selectedStateId ? items.find((item) => `${item.groupId}:${item.key}` === selectedStateId) : null), [items, selectedStateId]);
13
13
  const handleRowClick = (item) => selectStateId(`${item.groupId}:${item.key}`);
14
14
  const onClose = () => selectStateId(undefined);
15
- return (_jsxs("div", { className: "flex flex-row gap-4 h-full", children: [selectedItem && _jsx(StateDetail, { state: selectedItem, onClose: onClose }), _jsxs(Table, { children: [_jsx(TableHeader, { className: "sticky top-0 bg-background", children: _jsxs(TableRow, { children: [_jsx(TableHead, { className: "rounded-0", children: "Group ID" }), _jsx(TableHead, { children: "Key" }), _jsx(TableHead, { children: "Type" })] }) }), _jsx(TableBody, { children: items.map((item) => (_jsxs(TableRow, { "data-testid": `item-${item}`, onClick: () => handleRowClick(item), className: cn('font-mono font-semibold', selectedItem === item
15
+ return (_jsxs("div", { className: "flex flex-row gap-4 h-full", children: [selectedItem && _jsx(StateDetail, { state: selectedItem, onClose: onClose }), _jsxs(Table, { children: [_jsx(TableHeader, { className: "sticky top-0 bg-background", children: _jsxs(TableRow, { children: [_jsx(TableHead, { className: "rounded-0", children: "Group ID" }), _jsx(TableHead, { children: "Key" }), _jsx(TableHead, { children: "Type" })] }) }), _jsx(TableBody, { children: items.map((item) => (_jsxs(TableRow, { "data-testid": `item-${item}`, onClick: () => handleRowClick(item), className: cn('font-mono font-semibold cursor-pointer border-0', selectedItem === item
16
16
  ? 'bg-muted-foreground/10 hover:bg-muted-foreground/20'
17
17
  : 'hover:bg-muted-foreground/10'), children: [_jsx(TableCell, { className: "hover:bg-transparent", children: item.groupId }), _jsx(TableCell, { className: "hover:bg-transparent", children: item.key }), _jsx(TableCell, { className: "hover:bg-transparent", children: item.type })] }, `${item.groupId}:${item.key}`))) })] })] }));
18
18
  };