zudoku 0.3.1-dev.2 → 0.3.1-dev.21

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 (126) hide show
  1. package/LICENSE.md +21 -0
  2. package/dist/config/config.d.ts +0 -3
  3. package/dist/lib/authentication/authentication.d.ts +1 -0
  4. package/dist/lib/authentication/components/CallbackHandler.d.ts +3 -0
  5. package/dist/lib/authentication/components/CallbackHandler.js +34 -0
  6. package/dist/lib/authentication/components/CallbackHandler.js.map +1 -0
  7. package/dist/lib/authentication/providers/auth0.js +11 -6
  8. package/dist/lib/authentication/providers/auth0.js.map +1 -1
  9. package/dist/lib/authentication/providers/openid.d.ts +4 -13
  10. package/dist/lib/authentication/providers/openid.js +50 -49
  11. package/dist/lib/authentication/providers/openid.js.map +1 -1
  12. package/dist/lib/components/Header.js +2 -4
  13. package/dist/lib/components/Header.js.map +1 -1
  14. package/dist/lib/components/Layout.js +5 -1
  15. package/dist/lib/components/Layout.js.map +1 -1
  16. package/dist/lib/components/context/ZudokuProvider.js +1 -3
  17. package/dist/lib/components/context/ZudokuProvider.js.map +1 -1
  18. package/dist/lib/core/DevPortalContext.d.ts +1 -4
  19. package/dist/lib/core/DevPortalContext.js +2 -2
  20. package/dist/lib/core/DevPortalContext.js.map +1 -1
  21. package/dist/lib/core/plugins.d.ts +2 -4
  22. package/dist/lib/core/plugins.js.map +1 -1
  23. package/dist/lib/plugins/openapi/OperationList.js +3 -4
  24. package/dist/lib/plugins/openapi/OperationList.js.map +1 -1
  25. package/dist/lib/plugins/openapi/OperationListItem.js +2 -3
  26. package/dist/lib/plugins/openapi/OperationListItem.js.map +1 -1
  27. package/dist/lib/plugins/openapi/ParameterListItem.js +1 -1
  28. package/dist/lib/plugins/openapi/ParameterListItem.js.map +1 -1
  29. package/dist/lib/plugins/openapi/playground/PlaygroundDialog.js +2 -2
  30. package/dist/lib/plugins/openapi/playground/PlaygroundDialog.js.map +1 -1
  31. package/dist/lib/plugins/openapi/schema/LogicalGroup/LogicalGroup.d.ts +9 -0
  32. package/dist/lib/plugins/openapi/schema/LogicalGroup/LogicalGroup.js +14 -0
  33. package/dist/lib/plugins/openapi/schema/LogicalGroup/LogicalGroup.js.map +1 -0
  34. package/dist/lib/plugins/openapi/schema/LogicalGroup/LogicalGroupConnector.d.ts +6 -0
  35. package/dist/lib/plugins/openapi/schema/LogicalGroup/LogicalGroupConnector.js +17 -0
  36. package/dist/lib/plugins/openapi/schema/LogicalGroup/LogicalGroupConnector.js.map +1 -0
  37. package/dist/lib/plugins/openapi/schema/LogicalGroup/LogicalGroupItem.d.ts +7 -0
  38. package/dist/lib/plugins/openapi/schema/LogicalGroup/LogicalGroupItem.js +10 -0
  39. package/dist/lib/plugins/openapi/schema/LogicalGroup/LogicalGroupItem.js.map +1 -0
  40. package/dist/lib/plugins/openapi/schema/SchemaComponents.d.ts +3 -3
  41. package/dist/lib/plugins/openapi/schema/SchemaComponents.js +15 -15
  42. package/dist/lib/plugins/openapi/schema/SchemaComponents.js.map +1 -1
  43. package/dist/lib/plugins/openapi/schema/SchemaView.js +1 -1
  44. package/dist/lib/plugins/openapi/schema/SchemaView.js.map +1 -1
  45. package/dist/lib/plugins/openapi/schema/utils.d.ts +6 -0
  46. package/dist/lib/plugins/openapi/schema/utils.js +5 -0
  47. package/dist/lib/plugins/openapi/schema/utils.js.map +1 -1
  48. package/lib/{AuthenticationPlugin-CH5NSVOu.js → AuthenticationPlugin-owbEUimP.js} +3 -3
  49. package/lib/{AuthenticationPlugin-CH5NSVOu.js.map → AuthenticationPlugin-owbEUimP.js.map} +1 -1
  50. package/lib/{CategoryHeading-z15xh7Jb.js → CategoryHeading-DnPprxtD.js} +2 -2
  51. package/lib/{CategoryHeading-z15xh7Jb.js.map → CategoryHeading-DnPprxtD.js.map} +1 -1
  52. package/lib/{Combination-DTfV-c98.js → Combination-DruV0zX_.js} +3 -3
  53. package/lib/{Combination-DTfV-c98.js.map → Combination-DruV0zX_.js.map} +1 -1
  54. package/lib/ErrorPage-PUg985n_.js +18 -0
  55. package/lib/ErrorPage-PUg985n_.js.map +1 -0
  56. package/lib/{Input-DB9VROFR.js → Input-CBfi9Yjc.js} +5 -4
  57. package/lib/{Input-DB9VROFR.js.map → Input-CBfi9Yjc.js.map} +1 -1
  58. package/lib/{Markdown-CEccPMI_.js → Markdown-Chb9VIBv.js} +2 -2
  59. package/lib/{Markdown-CEccPMI_.js.map → Markdown-Chb9VIBv.js.map} +1 -1
  60. package/lib/{MdxPage-CnqOoqvp.js → MdxPage-CIBHMwTd.js} +5 -5
  61. package/lib/{MdxPage-CnqOoqvp.js.map → MdxPage-CIBHMwTd.js.map} +1 -1
  62. package/lib/OperationList-CZiSz5JH.js +590 -0
  63. package/lib/OperationList-CZiSz5JH.js.map +1 -0
  64. package/lib/{Route-DfAFiR7v.js → Route-Cle-r-bq.js} +4 -4
  65. package/lib/{Route-DfAFiR7v.js.map → Route-Cle-r-bq.js.map} +1 -1
  66. package/lib/{Spinner-BT_AYFrA.js → SidebarBadge-Ba0PhibA.js} +66 -76
  67. package/lib/SidebarBadge-Ba0PhibA.js.map +1 -0
  68. package/lib/{SlotletProvider-ByLSCZQa.js → SlotletProvider-Dq80og6-.js} +4 -4
  69. package/lib/{SlotletProvider-ByLSCZQa.js.map → SlotletProvider-Dq80og6-.js.map} +1 -1
  70. package/lib/Spinner-CvXZ7QK4.js +15 -0
  71. package/lib/Spinner-CvXZ7QK4.js.map +1 -0
  72. package/lib/{ZudokuContext-BIZ8zHbZ.js → ZudokuContext-BQ45UjcB.js} +2 -2
  73. package/lib/{ZudokuContext-BIZ8zHbZ.js.map → ZudokuContext-BQ45UjcB.js.map} +1 -1
  74. package/lib/{index-D-9zqIOh.js → index-Br1MQPxy.js} +595 -587
  75. package/lib/index-Br1MQPxy.js.map +1 -0
  76. package/lib/{index-Dz4LyXZI.js → index-DCJ9wEIV.js} +3 -3
  77. package/lib/{index-Dz4LyXZI.js.map → index-DCJ9wEIV.js.map} +1 -1
  78. package/lib/{index-7kcHaXD6.js → index-Yjb2PyPF.js} +4 -4
  79. package/lib/{index-7kcHaXD6.js.map → index-Yjb2PyPF.js.map} +1 -1
  80. package/lib/{urql-DrBfkb92.js → urql-YhcsXYy8.js} +2 -2
  81. package/lib/urql-YhcsXYy8.js.map +1 -0
  82. package/lib/{utils-Bh4upQ0e.js → utils-pDHePxa0.js} +3 -3
  83. package/lib/{utils-Bh4upQ0e.js.map → utils-pDHePxa0.js.map} +1 -1
  84. package/lib/zudoku.auth-auth0.js +24 -22
  85. package/lib/zudoku.auth-auth0.js.map +1 -1
  86. package/lib/zudoku.auth-clerk.js +1 -1
  87. package/lib/zudoku.auth-openid.js +527 -469
  88. package/lib/zudoku.auth-openid.js.map +1 -1
  89. package/lib/zudoku.components.js +434 -443
  90. package/lib/zudoku.components.js.map +1 -1
  91. package/lib/zudoku.openapi-worker.js +1 -1
  92. package/lib/zudoku.plugin-api-keys.js +6 -6
  93. package/lib/zudoku.plugin-custom-page.js +1 -1
  94. package/lib/zudoku.plugin-markdown.js +1 -1
  95. package/lib/zudoku.plugin-openapi.js +8 -7
  96. package/lib/zudoku.plugin-openapi.js.map +1 -1
  97. package/package.json +80 -100
  98. package/src/app/main.css +26 -1
  99. package/src/lib/authentication/authentication.ts +1 -0
  100. package/src/lib/authentication/components/CallbackHandler.tsx +59 -0
  101. package/src/lib/authentication/providers/auth0.tsx +13 -7
  102. package/src/lib/authentication/providers/openid.tsx +56 -58
  103. package/src/lib/components/Header.tsx +3 -10
  104. package/src/lib/components/Layout.tsx +6 -1
  105. package/src/lib/components/context/ZudokuProvider.tsx +1 -4
  106. package/src/lib/core/DevPortalContext.ts +2 -7
  107. package/src/lib/core/plugins.ts +1 -2
  108. package/src/lib/plugins/openapi/OperationList.tsx +3 -7
  109. package/src/lib/plugins/openapi/OperationListItem.tsx +3 -4
  110. package/src/lib/plugins/openapi/ParameterListItem.tsx +1 -1
  111. package/src/lib/plugins/openapi/playground/PlaygroundDialog.tsx +27 -5
  112. package/src/lib/plugins/openapi/schema/LogicalGroup/LogicalGroup.tsx +47 -0
  113. package/src/lib/plugins/openapi/schema/LogicalGroup/LogicalGroupConnector.tsx +54 -0
  114. package/src/lib/plugins/openapi/schema/LogicalGroup/LogicalGroupItem.tsx +30 -0
  115. package/src/lib/plugins/openapi/schema/SchemaComponents.tsx +41 -41
  116. package/src/lib/plugins/openapi/schema/SchemaView.tsx +4 -4
  117. package/src/lib/plugins/openapi/schema/utils.ts +8 -0
  118. package/dist/lib/plugins/openapi/util/prose.d.ts +0 -1
  119. package/dist/lib/plugins/openapi/util/prose.js +0 -4
  120. package/dist/lib/plugins/openapi/util/prose.js.map +0 -1
  121. package/lib/OperationList-Cxiw2Z-v.js +0 -457
  122. package/lib/OperationList-Cxiw2Z-v.js.map +0 -1
  123. package/lib/Spinner-BT_AYFrA.js.map +0 -1
  124. package/lib/index-D-9zqIOh.js.map +0 -1
  125. package/lib/urql-DrBfkb92.js.map +0 -1
  126. package/src/lib/plugins/openapi/util/prose.ts +0 -7
@@ -1,13 +1,12 @@
1
1
  import logger from "loglevel";
2
2
  import * as oauth from "oauth4webapi";
3
- import { NavigateFunction } from "react-router-dom";
4
3
  import { OpenIDAuthenticationConfig } from "../../../config/config.js";
5
- import { CommonPlugin } from "../../core/plugins.js";
6
4
  import {
7
5
  AuthenticationProvider,
8
6
  AuthenticationProviderInitializer,
9
7
  } from "../authentication.js";
10
8
  import { AuthenticationPlugin } from "../AuthenticationPlugin.js";
9
+ import { CallbackHandler } from "../components/CallbackHandler.js";
11
10
  import { AuthorizationError, OAuthAuthorizationError } from "../errors.js";
12
11
  import { useAuthState, UserProfile } from "../state.js";
13
12
 
@@ -23,7 +22,7 @@ interface TokenState {
23
22
  class OpenIdAuthPlugin extends AuthenticationPlugin {
24
23
  constructor(
25
24
  private callbackUrlPath: string,
26
- public initialize?: CommonPlugin["initialize"],
25
+ private handleCallback: () => Promise<string>,
27
26
  ) {
28
27
  super();
29
28
  }
@@ -32,7 +31,7 @@ class OpenIdAuthPlugin extends AuthenticationPlugin {
32
31
  ...super.getRoutes(),
33
32
  {
34
33
  path: this.callbackUrlPath,
35
- element: <div />,
34
+ element: <CallbackHandler handleCallback={this.handleCallback} />,
36
35
  },
37
36
  ];
38
37
  }
@@ -41,11 +40,8 @@ class OpenIdAuthPlugin extends AuthenticationPlugin {
41
40
  export class OpenIDAuthenticationProvider implements AuthenticationProvider {
42
41
  protected client: oauth.Client;
43
42
  protected issuer: string;
44
- protected authorizationEndpoint: string | undefined;
45
- protected tokenEndpoint: string | undefined;
46
43
 
47
44
  protected authorizationServer: oauth.AuthorizationServer | undefined;
48
- protected tokens: TokenState | undefined;
49
45
 
50
46
  protected callbackUrlPath = "/oauth/callback";
51
47
  protected logoutRedirectUrlPath = "/";
@@ -61,8 +57,6 @@ export class OpenIDAuthenticationProvider implements AuthenticationProvider {
61
57
  constructor({
62
58
  issuer,
63
59
  audience,
64
- authorizationEndpoint,
65
- tokenEndpoint,
66
60
  clientId,
67
61
  redirectToAfterSignUp,
68
62
  redirectToAfterSignIn,
@@ -74,8 +68,6 @@ export class OpenIDAuthenticationProvider implements AuthenticationProvider {
74
68
  };
75
69
  this.audience = audience;
76
70
  this.issuer = issuer;
77
- this.authorizationEndpoint = authorizationEndpoint;
78
- this.tokenEndpoint = tokenEndpoint;
79
71
  this.redirectToAfterSignUp = redirectToAfterSignUp ?? "/";
80
72
  this.redirectToAfterSignIn = redirectToAfterSignIn ?? "/";
81
73
  this.redirectToAfterSignOut = redirectToAfterSignOut ?? "/";
@@ -83,21 +75,12 @@ export class OpenIDAuthenticationProvider implements AuthenticationProvider {
83
75
 
84
76
  protected async getAuthServer() {
85
77
  if (!this.authorizationServer) {
86
- if (this.tokenEndpoint && this.authorizationEndpoint) {
87
- this.authorizationServer = {
88
- issuer: new URL(this.authorizationEndpoint!).origin,
89
- authorization_endpoint: this.authorizationEndpoint,
90
- token_endpoint: this.tokenEndpoint,
91
- code_challenge_methods_supported: [],
92
- };
93
- } else {
94
- const issuerUrl = new URL(this.issuer);
95
- const response = await oauth.discoveryRequest(issuerUrl);
96
- this.authorizationServer = await oauth.processDiscoveryResponse(
97
- issuerUrl,
98
- response,
99
- );
100
- }
78
+ const issuerUrl = new URL(this.issuer);
79
+ const response = await oauth.discoveryRequest(issuerUrl);
80
+ this.authorizationServer = await oauth.processDiscoveryResponse(
81
+ issuerUrl,
82
+ response,
83
+ );
101
84
  }
102
85
  return this.authorizationServer;
103
86
  }
@@ -118,13 +101,13 @@ export class OpenIDAuthenticationProvider implements AuthenticationProvider {
118
101
  throw new AuthorizationError("No expires_in in response");
119
102
  }
120
103
 
121
- this.tokens = {
104
+ const tokens: TokenState = {
122
105
  accessToken: response.access_token,
123
106
  refreshToken: response.refresh_token,
124
107
  expiresOn: new Date(Date.now() + response.expires_in * 1000),
125
108
  tokenType: response.token_type,
126
109
  };
127
- sessionStorage.setItem("openid-token", JSON.stringify(this.tokens));
110
+ sessionStorage.setItem("token-state", JSON.stringify(tokens));
128
111
  }
129
112
 
130
113
  async signUp({ redirectTo }: { redirectTo?: string } = {}) {
@@ -178,7 +161,7 @@ export class OpenIDAuthenticationProvider implements AuthenticationProvider {
178
161
  authorizationUrl.searchParams.set("client_id", this.client.client_id);
179
162
  authorizationUrl.searchParams.set("redirect_uri", redirectUrl.toString());
180
163
  authorizationUrl.searchParams.set("response_type", "code");
181
- authorizationUrl.searchParams.set("scope", "openid+profile+email");
164
+ authorizationUrl.searchParams.set("scope", "openid profile email");
182
165
  authorizationUrl.searchParams.set("code_challenge", codeChallenge);
183
166
  authorizationUrl.searchParams.set(
184
167
  "code_challenge_method",
@@ -211,11 +194,14 @@ export class OpenIDAuthenticationProvider implements AuthenticationProvider {
211
194
 
212
195
  async getAccessToken(): Promise<string> {
213
196
  const as = await this.getAuthServer();
214
- if (!this.tokens) {
197
+ const tokenState = sessionStorage.getItem("token-state");
198
+ if (!tokenState) {
215
199
  throw new AuthorizationError("User is not authenticated");
216
200
  }
217
- if (this.tokens.expiresOn < new Date()) {
218
- if (!this.tokens.refreshToken) {
201
+
202
+ const state = JSON.parse(tokenState) as TokenState;
203
+ if (state.expiresOn < new Date()) {
204
+ if (!state.refreshToken) {
219
205
  await this.signIn();
220
206
  return "";
221
207
  }
@@ -223,7 +209,7 @@ export class OpenIDAuthenticationProvider implements AuthenticationProvider {
223
209
  const request = await oauth.refreshTokenGrantRequest(
224
210
  as,
225
211
  this.client,
226
- this.tokens.refreshToken,
212
+ state.refreshToken,
227
213
  );
228
214
  const response = await oauth.processRefreshTokenResponse(
229
215
  as,
@@ -231,10 +217,16 @@ export class OpenIDAuthenticationProvider implements AuthenticationProvider {
231
217
  request,
232
218
  );
233
219
 
220
+ if (!response.access_token) {
221
+ throw new AuthorizationError("No access token in response");
222
+ }
223
+
234
224
  this.setTokensFromResponse(response);
235
- }
236
225
 
237
- return this.tokens.accessToken;
226
+ return response.access_token.toString();
227
+ } else {
228
+ return state.accessToken;
229
+ }
238
230
  }
239
231
 
240
232
  signOut = async () => {
@@ -243,7 +235,7 @@ export class OpenIDAuthenticationProvider implements AuthenticationProvider {
243
235
  isPending: false,
244
236
  profile: undefined,
245
237
  });
246
- localStorage.removeItem("auto-login");
238
+ sessionStorage.clear();
247
239
 
248
240
  const as = await this.getAuthServer();
249
241
 
@@ -279,9 +271,8 @@ export class OpenIDAuthenticationProvider implements AuthenticationProvider {
279
271
  // Authorization Code Grant Request & Response
280
272
  const codeVerifier = sessionStorage.getItem(CODE_VERIFIER_KEY);
281
273
  sessionStorage.removeItem(CODE_VERIFIER_KEY);
282
-
283
274
  if (!codeVerifier) {
284
- return "/";
275
+ throw new AuthorizationError("No code verifier found in state.");
285
276
  }
286
277
 
287
278
  const authServer = await this.getAuthServer();
@@ -320,7 +311,7 @@ export class OpenIDAuthenticationProvider implements AuthenticationProvider {
320
311
  // }
321
312
  // throw new Error(); // Handle WWW-Authenticate Challenges as needed
322
313
  // }
323
- const oauthResult = await oauth.processAuthorizationCodeOAuth2Response(
314
+ const oauthResult = await oauth.processAuthorizationCodeOpenIDResponse(
324
315
  authServer,
325
316
  this.client,
326
317
  response,
@@ -351,29 +342,36 @@ export class OpenIDAuthenticationProvider implements AuthenticationProvider {
351
342
  profile,
352
343
  });
353
344
 
354
- localStorage.setItem("auto-login", "1");
345
+ sessionStorage.setItem(
346
+ "profile-state",
347
+ JSON.stringify(useAuthState.getState().profile),
348
+ );
355
349
 
356
- return sessionStorage.getItem("redirect-to") ?? "/";
350
+ const redirectTo = sessionStorage.getItem("redirect-to") ?? "/";
351
+ sessionStorage.removeItem("redirect-to");
352
+ return redirectTo;
357
353
  };
358
354
 
355
+ pageLoad(): void {
356
+ const profileState = sessionStorage.getItem("profile-state");
357
+ if (profileState) {
358
+ try {
359
+ const profile = JSON.parse(profileState);
360
+ useAuthState.setState({
361
+ isAuthenticated: true,
362
+ isPending: false,
363
+ profile,
364
+ });
365
+ } catch (err) {
366
+ logger.error("Error parsing auth state", err);
367
+ }
368
+ }
369
+ }
370
+
359
371
  getAuthenticationPlugin() {
360
- return new OpenIdAuthPlugin(
361
- this.callbackUrlPath,
362
- async (_, options: { navigate: NavigateFunction }) => {
363
- if (typeof window === "undefined") return;
364
-
365
- if (localStorage.getItem("auto-login")) {
366
- localStorage.removeItem("auto-login");
367
-
368
- await this.authorize({ redirectTo: window.location.pathname });
369
- } else if (window.location.pathname === "/oauth/callback") {
370
- const redirect = await this.handleCallback();
371
- if (redirect) {
372
- options.navigate(redirect);
373
- }
374
- }
375
- },
376
- );
372
+ // TODO: This API is a bit messy, we need to refactor auth plugins/providers
373
+ // to remove the extra layers of abstraction.
374
+ return new OpenIdAuthPlugin(this.callbackUrlPath, this.handleCallback);
377
375
  }
378
376
  }
379
377
 
@@ -1,6 +1,5 @@
1
1
  import { MoonStarIcon, SunIcon } from "lucide-react";
2
2
  import { memo } from "react";
3
-
4
3
  import { Link, useLocation } from "react-router-dom";
5
4
  import { useAuth } from "../authentication/hook.js";
6
5
  import { isProfileMenuPlugin, ProfileNavigationItem } from "../core/plugins.js";
@@ -45,6 +44,7 @@ const RecursiveMenu = ({ item }: { item: ProfileNavigationItem }) => {
45
44
  };
46
45
 
47
46
  export const Header = memo(function HeaderInner() {
47
+ const auth = useAuth();
48
48
  const [isDark, toggleTheme] = useTheme();
49
49
  const { isAuthenticated, profile, isAuthEnabled } = useAuth();
50
50
  const { pathname } = useLocation();
@@ -97,15 +97,8 @@ export const Header = memo(function HeaderInner() {
97
97
  <div className="items-center justify-self-end text-sm hidden lg:flex gap-2">
98
98
  <Slotlet name="head-navigation-start" />
99
99
  {isAuthEnabled && !isAuthenticated ? (
100
- <Button variant="ghost" asChild>
101
- <Link
102
- to={{
103
- pathname: "/signin",
104
- search: `?redirect=${encodeURIComponent(pathname)}`,
105
- }}
106
- >
107
- Login
108
- </Link>
100
+ <Button variant="ghost" onClick={() => auth.login()}>
101
+ Login
109
102
  </Button>
110
103
  ) : (
111
104
  accountItems.length > 0 && (
@@ -14,13 +14,18 @@ import { Spinner } from "./Spinner.js";
14
14
  export const Layout = ({ children }: { children?: ReactNode }) => {
15
15
  const location = useLocation();
16
16
  const { setActiveAnchor } = useViewportAnchor();
17
- const { meta } = useZudoku();
17
+ const { meta, authentication } = useZudoku();
18
18
 
19
19
  useScrollToAnchor();
20
20
  useScrollToTop();
21
21
 
22
22
  const previousLocationPath = useRef(location.pathname);
23
23
 
24
+ useEffect(() => {
25
+ // Initialize the authentication plugin
26
+ authentication?.pageLoad ? authentication.pageLoad() : null;
27
+ }, [authentication]);
28
+
24
29
  useEffect(() => {
25
30
  // always reset on location change
26
31
  if (location.pathname !== previousLocationPath.current) {
@@ -1,6 +1,5 @@
1
1
  import { useSuspenseQuery } from "@tanstack/react-query";
2
2
  import type { PropsWithChildren } from "react";
3
- import { useNavigate } from "react-router-dom";
4
3
  import { DevPortalContext } from "../../core/DevPortalContext.js";
5
4
  import { ZudokuReactContext } from "./ZudokuContext.js";
6
5
 
@@ -8,11 +7,9 @@ export const ZudokuProvider = ({
8
7
  children,
9
8
  context,
10
9
  }: PropsWithChildren<{ context: DevPortalContext }>) => {
11
- const navigate = useNavigate();
12
-
13
10
  useSuspenseQuery({
14
11
  queryFn: async () => {
15
- await context.initialize({ navigate });
12
+ await context.initialize();
16
13
  return true;
17
14
  },
18
15
  queryKey: ["zudoku-initialize"],
@@ -1,5 +1,4 @@
1
1
  import { QueryClient } from "@tanstack/react-query";
2
- import { NavigateFunction } from "react-router-dom";
3
2
  import type { SidebarConfig } from "../../config/validators/SidebarSchema.js";
4
3
  import { type AuthenticationProvider } from "../authentication/authentication.js";
5
4
  import type { ComponentsContextType } from "../components/context/ComponentsContext.js";
@@ -84,15 +83,11 @@ export class DevPortalContext {
84
83
  this.page = config.page;
85
84
  }
86
85
 
87
- initialize = async ({
88
- navigate,
89
- }: {
90
- navigate: NavigateFunction;
91
- }): Promise<void> => {
86
+ initialize = async (): Promise<void> => {
92
87
  await Promise.all(
93
88
  this.plugins
94
89
  .filter(needsInitialization)
95
- .map((plugin) => plugin.initialize?.(this, { navigate })),
90
+ .map((plugin) => plugin.initialize?.(this)),
96
91
  );
97
92
  };
98
93
 
@@ -1,5 +1,5 @@
1
1
  import { type ReactElement } from "react";
2
- import { NavigateFunction, type RouteObject } from "react-router-dom";
2
+ import { type RouteObject } from "react-router-dom";
3
3
  import type { Sidebar } from "../../config/validators/SidebarSchema.js";
4
4
  import { MdxComponentsType } from "../util/MdxComponents.js";
5
5
  import { DevPortalContext, type ApiIdentity } from "./DevPortalContext.js";
@@ -40,7 +40,6 @@ export type ProfileNavigationItem = {
40
40
  export interface CommonPlugin {
41
41
  initialize?: (
42
42
  context: DevPortalContext,
43
- options: { navigate: NavigateFunction },
44
43
  ) => Promise<void | boolean> | void | boolean;
45
44
  getHead?: () => ReactElement | undefined;
46
45
  getMdxComponents?: () => MdxComponentsType;
@@ -4,14 +4,13 @@ import { DeveloperHint } from "../../components/DeveloperHint.js";
4
4
  import { ErrorPage } from "../../components/ErrorPage.js";
5
5
  import { Heading } from "../../components/Heading.js";
6
6
  import { InlineCode } from "../../components/InlineCode.js";
7
- import { Markdown } from "../../components/Markdown.js";
7
+ import { Markdown, ProseClasses } from "../../components/Markdown.js";
8
8
  import { SyntaxHighlight } from "../../components/SyntaxHighlight.js";
9
9
  import { cn } from "../../util/cn.js";
10
10
  import { OperationListItem } from "./OperationListItem.js";
11
11
  import StaggeredRender from "./StaggeredRender.js";
12
12
  import { useOasConfig } from "./context.js";
13
13
  import { graphql } from "./graphql/index.js";
14
- import { SchemaProseClasses } from "./util/prose.js";
15
14
  import { useQuery } from "./util/urql.js";
16
15
 
17
16
  export const OperationsFragment = graphql(/* GraphQL */ `
@@ -124,10 +123,7 @@ export const OperationList = () => {
124
123
  return (
125
124
  <div className="pt-[--padding-content-top]">
126
125
  <div
127
- className={cn(
128
- SchemaProseClasses,
129
- "mb-16 max-w-full prose-img:max-w-prose",
130
- )}
126
+ className={cn(ProseClasses, "mb-16 max-w-full prose-img:max-w-prose")}
131
127
  >
132
128
  <CategoryHeading>Overview</CategoryHeading>
133
129
  <Heading level={1} id="description" registerSidebarAnchor>
@@ -142,7 +138,7 @@ export const OperationList = () => {
142
138
  {tag.name && <CategoryHeading>{tag.name}</CategoryHeading>}
143
139
  {tag.description && (
144
140
  <Markdown
145
- className={`${SchemaProseClasses} mt-2 mb-12`}
141
+ className={`${ProseClasses} max-w-full prose-img:max-w-prose w-full mt-2 mb-12`}
146
142
  content={tag.description}
147
143
  />
148
144
  )}
@@ -1,6 +1,6 @@
1
1
  import { useState } from "react";
2
2
  import { Heading } from "../../components/Heading.js";
3
- import { Markdown } from "../../components/Markdown.js";
3
+ import { Markdown, ProseClasses } from "../../components/Markdown.js";
4
4
  import { Tabs, TabsContent, TabsList, TabsTrigger } from "../../ui/Tabs.js";
5
5
  import { groupBy } from "../../util/groupBy.js";
6
6
  import { renderIf } from "../../util/renderIf.js";
@@ -9,7 +9,6 @@ import { ParameterList } from "./ParameterList.js";
9
9
  import { Sidecar } from "./Sidecar.js";
10
10
  import { FragmentType, useFragment } from "./graphql/index.js";
11
11
  import { SchemaView } from "./schema/SchemaView.js";
12
- import { SchemaProseClasses } from "./util/prose.js";
13
12
 
14
13
  export const PARAM_GROUPS = ["path", "query", "header", "cookie"] as const;
15
14
  export type ParameterGroup = (typeof PARAM_GROUPS)[number];
@@ -39,7 +38,7 @@ export const OperationListItem = ({
39
38
  </Heading>
40
39
  {operation.description && (
41
40
  <Markdown
42
- className={SchemaProseClasses}
41
+ className={`${ProseClasses} max-w-full prose-img:max-w-prose`}
43
42
  content={operation.description}
44
43
  />
45
44
  )}
@@ -89,7 +88,7 @@ export const OperationListItem = ({
89
88
  ))}
90
89
  </TabsList>
91
90
  )}
92
- <ul className="list-none m-0 px-0 overflow-hidden">
91
+ <ul className="list-none m-0 px-0">
93
92
  {operation.responses.map((response) => (
94
93
  <TabsContent
95
94
  value={response.statusCode}
@@ -55,7 +55,7 @@ export const ParameterListItem = ({
55
55
  {parameter.description && (
56
56
  <Markdown
57
57
  content={parameter.description}
58
- className="text-sm prose-p:my-1"
58
+ className="text-sm prose-p:my-1 prose-code:whitespace-pre-line"
59
59
  />
60
60
  )}
61
61
  </li>
@@ -1,5 +1,4 @@
1
1
  import { VisuallyHidden } from "@radix-ui/react-visually-hidden";
2
- import { CirclePlayIcon } from "lucide-react";
3
2
  import { type PropsWithChildren, useState } from "react";
4
3
  import {
5
4
  Dialog,
@@ -11,16 +10,39 @@ import { Playground, type PlaygroundContentProps } from "./Playground.js";
11
10
 
12
11
  export type PlaygroundDialogProps = PropsWithChildren<PlaygroundContentProps>;
13
12
 
13
+ const HeroPlayIcon = ({
14
+ className,
15
+ size = 16,
16
+ }: {
17
+ className?: string;
18
+ size?: number;
19
+ }) => (
20
+ <svg
21
+ xmlns="http://www.w3.org/2000/svg"
22
+ viewBox="0 0 24 24"
23
+ fill="currentColor"
24
+ className={className}
25
+ width={size}
26
+ height={size}
27
+ >
28
+ <path
29
+ fillRule="evenodd"
30
+ d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm14.024-.983a1.125 1.125 0 0 1 0 1.966l-5.603 3.113A1.125 1.125 0 0 1 9 15.113V8.887c0-.857.921-1.4 1.671-.983l5.603 3.113Z"
31
+ clipRule="evenodd"
32
+ />
33
+ </svg>
34
+ );
35
+
14
36
  const PlaygroundDialog = (props: PlaygroundDialogProps) => {
15
37
  const [open, setOpen] = useState(false);
16
38
  return (
17
39
  <Dialog onOpenChange={(open) => setOpen(open)}>
18
40
  <DialogTrigger asChild>
19
41
  {props.children ?? (
20
- <CirclePlayIcon
21
- className="cursor-pointer text-primary hover:text-primary/80"
22
- size={16}
23
- />
42
+ <button className="flex gap-1 items-center px-2 py-1 rounded-md bg-primary/80 hover:bg-primary transition text-primary-foreground text-xs">
43
+ Test
44
+ <HeroPlayIcon className="" size={14} />
45
+ </button>
24
46
  )}
25
47
  </DialogTrigger>
26
48
 
@@ -0,0 +1,47 @@
1
+ import * as Collapsible from "@radix-ui/react-collapsible";
2
+ import { SquareMinusIcon, SquarePlusIcon } from "lucide-react";
3
+ import type { SchemaObject } from "../../../../oas/parser/index.js";
4
+ import { Card } from "../../../../ui/Card.js";
5
+ import type { LogicalGroupType } from "../utils.js";
6
+ import { LogicalGroupItem } from "./LogicalGroupItem.js";
7
+
8
+ const typeLabel = {
9
+ AND: "All of",
10
+ OR: "Any of",
11
+ ONE: "One of",
12
+ };
13
+
14
+ export const LogicalGroup = ({
15
+ schemas,
16
+ type,
17
+ isOpen,
18
+ level,
19
+ toggleOpen,
20
+ }: {
21
+ schemas: SchemaObject[];
22
+ type: LogicalGroupType;
23
+ isOpen: boolean;
24
+ toggleOpen: () => void;
25
+ level: number;
26
+ }) => (
27
+ <Collapsible.Root open={isOpen} onOpenChange={toggleOpen} asChild>
28
+ <Card className="px-6">
29
+ <Collapsible.Trigger className="flex gap-2 items-center py-2 w-full text-sm text-muted-foreground -translate-x-1.5">
30
+ {isOpen ? <SquareMinusIcon size={14} /> : <SquarePlusIcon size={14} />}
31
+ <span>{typeLabel[type]}</span>
32
+ </Collapsible.Trigger>
33
+
34
+ <Collapsible.Content className="pb-4">
35
+ {schemas.map((subSchema, index) => (
36
+ // eslint-disable-next-line react/no-array-index-key
37
+ <LogicalGroupItem
38
+ key={index}
39
+ type={type}
40
+ schema={subSchema}
41
+ level={level}
42
+ />
43
+ ))}
44
+ </Collapsible.Content>
45
+ </Card>
46
+ </Collapsible.Root>
47
+ );
@@ -0,0 +1,54 @@
1
+ import {
2
+ ChevronDownIcon,
3
+ CircleDotIcon,
4
+ CircleFadingPlusIcon,
5
+ CircleIcon,
6
+ } from "lucide-react";
7
+ import { cn } from "../../../../util/cn.js";
8
+
9
+ import type { LogicalGroupType } from "../utils.js";
10
+
11
+ const iconMap = {
12
+ AND: <CircleFadingPlusIcon size={16} className="fill-card" />,
13
+ OR: <CircleDotIcon size={16} className="fill-card" />,
14
+ ONE: <CircleIcon size={14} className="fill-card" />,
15
+ } as const;
16
+
17
+ const colorClass = {
18
+ AND: "text-green-500 dark:text-green-300/60",
19
+ OR: "text-blue-400 dark:text-blue-500",
20
+ ONE: "text-purple-500 dark:text-purple-300/60",
21
+ } as const;
22
+
23
+ export const LogicalGroupConnector = ({
24
+ type,
25
+ isOpen,
26
+ className,
27
+ }: {
28
+ type: LogicalGroupType;
29
+ isOpen: boolean;
30
+ className?: string;
31
+ }) => {
32
+ return (
33
+ <div
34
+ className={cn(
35
+ colorClass[type],
36
+ "relative text-sm flex py-2",
37
+ "before:border-l before:absolute before:-top-2 before:-bottom-2 before:border-border before:border-dashed before:content-['']",
38
+ className,
39
+ )}
40
+ >
41
+ <div className="-translate-x-[7px] flex gap-1 items-center">
42
+ {iconMap[type]}
43
+ <div
44
+ className={cn(
45
+ "translate-y-px mx-px opacity-0 group-hover:opacity-100 transition",
46
+ !isOpen && "-rotate-90",
47
+ )}
48
+ >
49
+ <ChevronDownIcon size={16} />
50
+ </div>
51
+ </div>
52
+ </div>
53
+ );
54
+ };
@@ -0,0 +1,30 @@
1
+ import * as Collapsible from "@radix-ui/react-collapsible";
2
+ import { useState } from "react";
3
+ import type { SchemaObject } from "../../../../oas/parser/index.js";
4
+ import { SchemaView } from "../SchemaView.js";
5
+ import type { LogicalGroupType } from "../utils.js";
6
+ import { LogicalGroupConnector } from "./LogicalGroupConnector.js";
7
+
8
+ export const LogicalGroupItem = (props: {
9
+ type: LogicalGroupType;
10
+ schema: SchemaObject;
11
+ level: number;
12
+ }) => {
13
+ const [isOpen, setIsOpen] = useState(true);
14
+
15
+ return (
16
+ <Collapsible.Root
17
+ open={isOpen}
18
+ onOpenChange={() => setIsOpen((prev) => !prev)}
19
+ className="group"
20
+ >
21
+ <Collapsible.Trigger>
22
+ <LogicalGroupConnector type={props.type} isOpen={isOpen} />
23
+ </Collapsible.Trigger>
24
+ {!isOpen && <div className="wavy-line bg-border translate-y-1" />}
25
+ <Collapsible.Content>
26
+ <SchemaView schema={props.schema} level={props.level + 1} />
27
+ </Collapsible.Content>
28
+ </Collapsible.Root>
29
+ );
30
+ };