zudoku 0.63.1 → 0.64.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 (219) hide show
  1. package/client.d.ts +8 -0
  2. package/dist/config/config.d.ts +3 -1
  3. package/dist/config/validators/BuildSchema.d.ts +5 -4
  4. package/dist/config/validators/BuildSchema.js +5 -2
  5. package/dist/config/validators/BuildSchema.js.map +1 -1
  6. package/dist/config/validators/InputNavigationSchema.d.ts +20 -20
  7. package/dist/config/validators/validate.d.ts +10 -12
  8. package/dist/config/validators/validate.js +8 -8
  9. package/dist/config/validators/validate.js.map +1 -1
  10. package/dist/flat-config.d.ts +6 -1
  11. package/dist/lib/authentication/authentication.d.ts +14 -9
  12. package/dist/lib/authentication/components/OAuthErrorPage.js +1 -1
  13. package/dist/lib/authentication/components/OAuthErrorPage.js.map +1 -1
  14. package/dist/lib/authentication/components/SignIn.js +6 -5
  15. package/dist/lib/authentication/components/SignIn.js.map +1 -1
  16. package/dist/lib/authentication/components/SignOut.js +6 -6
  17. package/dist/lib/authentication/components/SignOut.js.map +1 -1
  18. package/dist/lib/authentication/components/SignUp.js +5 -5
  19. package/dist/lib/authentication/components/SignUp.js.map +1 -1
  20. package/dist/lib/authentication/hook.d.ts +3 -2
  21. package/dist/lib/authentication/hook.js +12 -8
  22. package/dist/lib/authentication/hook.js.map +1 -1
  23. package/dist/lib/authentication/providers/auth0.js +1 -1
  24. package/dist/lib/authentication/providers/auth0.js.map +1 -1
  25. package/dist/lib/authentication/providers/azureb2c.d.ts +4 -4
  26. package/dist/lib/authentication/providers/azureb2c.js +3 -3
  27. package/dist/lib/authentication/providers/azureb2c.js.map +1 -1
  28. package/dist/lib/authentication/providers/clerk.js +2 -2
  29. package/dist/lib/authentication/providers/clerk.js.map +1 -1
  30. package/dist/lib/authentication/providers/openid.d.ts +7 -7
  31. package/dist/lib/authentication/providers/openid.js +5 -3
  32. package/dist/lib/authentication/providers/openid.js.map +1 -1
  33. package/dist/lib/authentication/providers/supabase/SupabaseAuthUI.d.ts +8 -0
  34. package/dist/lib/authentication/providers/supabase/SupabaseAuthUI.js +39 -0
  35. package/dist/lib/authentication/providers/supabase/SupabaseAuthUI.js.map +1 -0
  36. package/dist/lib/authentication/providers/supabase.js +35 -31
  37. package/dist/lib/authentication/providers/supabase.js.map +1 -1
  38. package/dist/lib/authentication/state.d.ts +1 -5
  39. package/dist/lib/authentication/state.js +2 -14
  40. package/dist/lib/authentication/state.js.map +1 -1
  41. package/dist/lib/components/Zudoku.js +3 -3
  42. package/dist/lib/components/index.d.ts +2 -2
  43. package/dist/lib/components/navigation/Toc.js +1 -1
  44. package/dist/lib/components/navigation/Toc.js.map +1 -1
  45. package/dist/lib/core/RouteGuard.d.ts +1 -1
  46. package/dist/lib/core/RouteGuard.js +6 -14
  47. package/dist/lib/core/RouteGuard.js.map +1 -1
  48. package/dist/lib/core/__internal.d.ts +1 -1
  49. package/dist/lib/hooks/index.d.ts +2 -2
  50. package/dist/lib/plugins/openapi/playground/CollapsibleHeader.js +1 -1
  51. package/dist/lib/plugins/openapi/playground/CollapsibleHeader.js.map +1 -1
  52. package/dist/lib/plugins/openapi/playground/Playground.js +12 -2
  53. package/dist/lib/plugins/openapi/playground/Playground.js.map +1 -1
  54. package/dist/lib/plugins/openapi/playground/RequestLoginDialog.d.ts +2 -1
  55. package/dist/lib/plugins/openapi/playground/RequestLoginDialog.js +10 -2
  56. package/dist/lib/plugins/openapi/playground/RequestLoginDialog.js.map +1 -1
  57. package/dist/lib/plugins/openapi/playground/result-panel/Highlight.d.ts +1 -1
  58. package/dist/lib/plugins/openapi/playground/result-panel/ResponseStatusBar.js +1 -1
  59. package/dist/lib/plugins/openapi/playground/result-panel/ResponseStatusBar.js.map +1 -1
  60. package/dist/lib/plugins/openapi/playground/result-panel/ResponseTab.js +23 -21
  61. package/dist/lib/plugins/openapi/playground/result-panel/ResponseTab.js.map +1 -1
  62. package/dist/lib/plugins/openapi/playground/result-panel/ResultPanel.js +1 -1
  63. package/dist/lib/plugins/openapi/playground/result-panel/ResultPanel.js.map +1 -1
  64. package/dist/lib/plugins/openapi/playground/useRememberSkipLoginDialog.js +2 -0
  65. package/dist/lib/plugins/openapi/playground/useRememberSkipLoginDialog.js.map +1 -1
  66. package/dist/lib/shiki.d.ts +1 -1
  67. package/dist/lib/shiki.js +13 -2
  68. package/dist/lib/shiki.js.map +1 -1
  69. package/dist/lib/ui/Callout.d.ts +5 -5
  70. package/dist/lib/ui/Callout.js +5 -5
  71. package/dist/lib/ui/Callout.js.map +1 -1
  72. package/dist/lib/ui/EmbeddedCodeBlock.d.ts +3 -1
  73. package/dist/lib/ui/EmbeddedCodeBlock.js +2 -2
  74. package/dist/lib/ui/EmbeddedCodeBlock.js.map +1 -1
  75. package/dist/lib/ui/ReactComponentDoc.d.ts +1 -1
  76. package/dist/lib/ui/ReactComponentDoc.js +2 -2
  77. package/dist/lib/ui/ReactComponentDoc.js.map +1 -1
  78. package/dist/lib/ui/SyntaxHighlight.d.ts +1 -0
  79. package/dist/lib/ui/SyntaxHighlight.js.map +1 -1
  80. package/dist/lib/util/MdxComponents.d.ts +2 -1
  81. package/dist/lib/util/MdxComponents.js +3 -2
  82. package/dist/lib/util/MdxComponents.js.map +1 -1
  83. package/dist/lib/util/syncZustandState.d.ts +5 -0
  84. package/dist/lib/util/syncZustandState.js +14 -0
  85. package/dist/lib/util/syncZustandState.js.map +1 -0
  86. package/dist/vite/api/SchemaManager.d.ts +3 -1
  87. package/dist/vite/api/SchemaManager.js +22 -3
  88. package/dist/vite/api/SchemaManager.js.map +1 -1
  89. package/dist/vite/api/SchemaManager.test.js +2 -2
  90. package/dist/vite/api/SchemaManager.test.js.map +1 -1
  91. package/dist/vite/plugin-api.js +14 -5
  92. package/dist/vite/plugin-api.js.map +1 -1
  93. package/dist/vite/plugin-mdx.js +36 -30
  94. package/dist/vite/plugin-mdx.js.map +1 -1
  95. package/lib/{ErrorAlert-VBJ8aHH7.js → ErrorAlert-DE3Sf66a.js} +1711 -1772
  96. package/lib/ErrorAlert-DE3Sf66a.js.map +1 -0
  97. package/lib/{MdxPage-1xaNI6q_.js → MdxPage-DZfeC0QY.js} +8 -8
  98. package/lib/{MdxPage-1xaNI6q_.js.map → MdxPage-DZfeC0QY.js.map} +1 -1
  99. package/lib/{OAuthErrorPage-Du_4sFER.js → OAuthErrorPage-BycMozgn.js} +8 -8
  100. package/lib/{OAuthErrorPage-Du_4sFER.js.map → OAuthErrorPage-BycMozgn.js.map} +1 -1
  101. package/lib/{OasProvider-0xiB5cKH.js → OasProvider-Bf5zBDBY.js} +3 -3
  102. package/lib/{OasProvider-0xiB5cKH.js.map → OasProvider-Bf5zBDBY.js.map} +1 -1
  103. package/lib/{OperationList-BzWKTkNq.js → OperationList-Cmiw1xm2.js} +10 -10
  104. package/lib/{OperationList-BzWKTkNq.js.map → OperationList-Cmiw1xm2.js.map} +1 -1
  105. package/lib/{Pagination-DOcPyfy_.js → Pagination-CJszmeSA.js} +3 -3
  106. package/lib/{Pagination-DOcPyfy_.js.map → Pagination-CJszmeSA.js.map} +1 -1
  107. package/lib/RouteGuard-DhU3LRr1.js +81 -0
  108. package/lib/RouteGuard-DhU3LRr1.js.map +1 -0
  109. package/lib/{RouterError-fm21cqlj.js → RouterError-VDLnrFqF.js} +5 -5
  110. package/lib/{RouterError-fm21cqlj.js.map → RouterError-VDLnrFqF.js.map} +1 -1
  111. package/lib/{SchemaList-Dea2Skqv.js → SchemaList-xZSf3IMh.js} +7 -7
  112. package/lib/{SchemaList-Dea2Skqv.js.map → SchemaList-xZSf3IMh.js.map} +1 -1
  113. package/lib/{SchemaView-H9glU5P7.js → SchemaView-tHXTm5oM.js} +3 -3
  114. package/lib/{SchemaView-H9glU5P7.js.map → SchemaView-tHXTm5oM.js.map} +1 -1
  115. package/lib/{Select-CPoGZU_V.js → Select-C1DeCqKv.js} +3 -3
  116. package/lib/{Select-CPoGZU_V.js.map → Select-C1DeCqKv.js.map} +1 -1
  117. package/lib/SignUp-6SGx9Yyq.js +50 -0
  118. package/lib/SignUp-6SGx9Yyq.js.map +1 -0
  119. package/lib/{SyntaxHighlight-B0laqAqK.js → SyntaxHighlight-zvlnSnHB.js} +789 -778
  120. package/lib/{SyntaxHighlight-B0laqAqK.js.map → SyntaxHighlight-zvlnSnHB.js.map} +1 -1
  121. package/lib/{Toc-KzXCRqrX.js → Toc-Da9yp7lo.js} +5 -5
  122. package/lib/Toc-Da9yp7lo.js.map +1 -0
  123. package/lib/{ZudokuContext-BXTZApgy.js → ZudokuContext-BUZ5hkWB.js} +33 -31
  124. package/lib/ZudokuContext-BUZ5hkWB.js.map +1 -0
  125. package/lib/{chunk-PVWAREVJ-BO6B-RAk.js → chunk-PVWAREVJ-BMhpCH5D.js} +7 -7
  126. package/lib/{chunk-PVWAREVJ-BO6B-RAk.js.map → chunk-PVWAREVJ-BMhpCH5D.js.map} +1 -1
  127. package/lib/{circular-OGtD_zFg.js → circular-DvuimBGQ.js} +2 -2
  128. package/lib/{circular-OGtD_zFg.js.map → circular-DvuimBGQ.js.map} +1 -1
  129. package/lib/{createServer-DyUc5BPr.js → createServer-D9UvCoDf.js} +4 -4
  130. package/lib/{createServer-DyUc5BPr.js.map → createServer-D9UvCoDf.js.map} +1 -1
  131. package/lib/{errors-7_i0Oyw4.js → errors-CuGgh3hf.js} +2 -2
  132. package/lib/{errors-7_i0Oyw4.js.map → errors-CuGgh3hf.js.map} +1 -1
  133. package/lib/hook-CMeoxziF.js +40 -0
  134. package/lib/hook-CMeoxziF.js.map +1 -0
  135. package/lib/{index-CLJGtw_S.js → index-B1rmok4X.js} +7 -7
  136. package/lib/{index-CLJGtw_S.js.map → index-B1rmok4X.js.map} +1 -1
  137. package/lib/{index-PdgTSEkk.js → index-Cr9_YzOZ.js} +853 -793
  138. package/lib/index-Cr9_YzOZ.js.map +1 -0
  139. package/lib/{index-C5L4favO.js → index-rYHsvtTo.js} +2 -2
  140. package/lib/{index-C5L4favO.js.map → index-rYHsvtTo.js.map} +1 -1
  141. package/lib/{mutation-CdGPxHNX.js → mutation-BSU0xu4m.js} +2 -2
  142. package/lib/{mutation-CdGPxHNX.js.map → mutation-BSU0xu4m.js.map} +1 -1
  143. package/lib/ui/Callout.js +18 -18
  144. package/lib/ui/Callout.js.map +1 -1
  145. package/lib/ui/CodeBlock.js +217 -7
  146. package/lib/ui/CodeBlock.js.map +1 -1
  147. package/lib/ui/EmbeddedCodeBlock.js +22 -19
  148. package/lib/ui/EmbeddedCodeBlock.js.map +1 -1
  149. package/lib/ui/ReactComponentDoc.js +13 -13
  150. package/lib/ui/ReactComponentDoc.js.map +1 -1
  151. package/lib/ui/SyntaxHighlight.js +3 -3
  152. package/lib/{useExposedProps-Cd7Yg_uG.js → useExposedProps-U3pmsHaG.js} +2 -2
  153. package/lib/{useExposedProps-Cd7Yg_uG.js.map → useExposedProps-U3pmsHaG.js.map} +1 -1
  154. package/lib/zudoku.__internal.js +7 -7
  155. package/lib/zudoku.auth-auth0.js +13 -13
  156. package/lib/zudoku.auth-auth0.js.map +1 -1
  157. package/lib/zudoku.auth-azureb2c.js +28 -28
  158. package/lib/zudoku.auth-azureb2c.js.map +1 -1
  159. package/lib/zudoku.auth-clerk.js +40 -40
  160. package/lib/zudoku.auth-clerk.js.map +1 -1
  161. package/lib/zudoku.auth-openid.js +53 -56
  162. package/lib/zudoku.auth-openid.js.map +1 -1
  163. package/lib/zudoku.auth-supabase.js +111 -52
  164. package/lib/zudoku.auth-supabase.js.map +1 -1
  165. package/lib/zudoku.components.js +6 -6
  166. package/lib/zudoku.hooks.js +4 -4
  167. package/lib/zudoku.plugin-api-catalog.js +5 -5
  168. package/lib/zudoku.plugin-api-keys.js +99 -99
  169. package/lib/zudoku.plugin-custom-pages.js +1 -1
  170. package/lib/zudoku.plugin-markdown.js +1 -1
  171. package/lib/zudoku.plugin-openapi.js +3 -3
  172. package/lib/zudoku.plugin-redirect.js +1 -1
  173. package/lib/zudoku.plugin-search-pagefind.js +38 -38
  174. package/lib/zudoku.plugin-search-pagefind.js.map +1 -1
  175. package/lib/zudoku.router.js +2 -2
  176. package/package.json +15 -7
  177. package/src/app/main.css +19 -9
  178. package/src/lib/authentication/authentication.ts +19 -3
  179. package/src/lib/authentication/components/OAuthErrorPage.tsx +1 -1
  180. package/src/lib/authentication/components/SignIn.tsx +7 -5
  181. package/src/lib/authentication/components/SignOut.tsx +7 -6
  182. package/src/lib/authentication/components/SignUp.tsx +5 -8
  183. package/src/lib/authentication/hook.ts +21 -10
  184. package/src/lib/authentication/providers/auth0.tsx +2 -1
  185. package/src/lib/authentication/providers/azureb2c.tsx +10 -3
  186. package/src/lib/authentication/providers/clerk.tsx +9 -2
  187. package/src/lib/authentication/providers/openid.tsx +20 -15
  188. package/src/lib/authentication/providers/supabase/SupabaseAuthUI.tsx +75 -0
  189. package/src/lib/authentication/providers/supabase.tsx +59 -43
  190. package/src/lib/authentication/state.ts +3 -23
  191. package/src/lib/components/Zudoku.tsx +3 -3
  192. package/src/lib/components/navigation/Toc.tsx +3 -3
  193. package/src/lib/core/RouteGuard.tsx +33 -13
  194. package/src/lib/plugins/openapi/playground/CollapsibleHeader.tsx +2 -2
  195. package/src/lib/plugins/openapi/playground/Playground.tsx +13 -2
  196. package/src/lib/plugins/openapi/playground/RequestLoginDialog.tsx +20 -1
  197. package/src/lib/plugins/openapi/playground/result-panel/ResponseStatusBar.tsx +2 -2
  198. package/src/lib/plugins/openapi/playground/result-panel/ResponseTab.tsx +102 -58
  199. package/src/lib/plugins/openapi/playground/result-panel/ResultPanel.tsx +1 -1
  200. package/src/lib/plugins/openapi/playground/useRememberSkipLoginDialog.tsx +3 -0
  201. package/src/lib/shiki.ts +16 -2
  202. package/src/lib/ui/Callout.tsx +10 -5
  203. package/src/lib/ui/EmbeddedCodeBlock.tsx +6 -3
  204. package/src/lib/ui/ReactComponentDoc.tsx +17 -17
  205. package/src/lib/ui/SyntaxHighlight.tsx +6 -1
  206. package/src/lib/util/MdxComponents.tsx +3 -5
  207. package/src/lib/util/syncZustandState.ts +22 -0
  208. package/lib/CodeBlock-CanTUJLl.js +0 -221
  209. package/lib/CodeBlock-CanTUJLl.js.map +0 -1
  210. package/lib/ErrorAlert-VBJ8aHH7.js.map +0 -1
  211. package/lib/RouteGuard-Bg0Lu0OU.js +0 -56
  212. package/lib/RouteGuard-Bg0Lu0OU.js.map +0 -1
  213. package/lib/SignUp-DUZ4yUmQ.js +0 -56
  214. package/lib/SignUp-DUZ4yUmQ.js.map +0 -1
  215. package/lib/Toc-KzXCRqrX.js.map +0 -1
  216. package/lib/ZudokuContext-BXTZApgy.js.map +0 -1
  217. package/lib/hook-CAebs2rv.js +0 -31
  218. package/lib/hook-CAebs2rv.js.map +0 -1
  219. package/lib/index-PdgTSEkk.js.map +0 -1
@@ -1,4 +1,6 @@
1
+ import { useNavigate } from "react-router";
1
2
  import { useZudoku } from "../components/context/ZudokuContext.js";
3
+ import type { AuthActionOptions } from "./authentication.js";
2
4
  import { useAuthState } from "./state.js";
3
5
 
4
6
  export type UseAuthReturn = ReturnType<typeof useAuth>;
@@ -7,19 +9,24 @@ export const useAuth = () => {
7
9
  const { authentication } = useZudoku();
8
10
  const authState = useAuthState();
9
11
  const isAuthEnabled = typeof authentication !== "undefined";
12
+ const navigate = useNavigate();
10
13
 
11
14
  return {
12
15
  isAuthEnabled,
13
16
  ...authState,
14
17
 
15
- login: async () => {
18
+ login: async (options?: AuthActionOptions) => {
16
19
  if (!isAuthEnabled) {
17
20
  throw new Error("Authentication is not enabled.");
18
21
  }
19
22
  // TODO: Should handle errors/state
20
- await authentication.signIn({
21
- redirectTo: window.location.href,
22
- });
23
+ await authentication.signIn(
24
+ { navigate },
25
+ {
26
+ ...options,
27
+ redirectTo: options?.redirectTo ?? window.location.href,
28
+ },
29
+ );
23
30
  },
24
31
 
25
32
  logout: async () => {
@@ -27,19 +34,23 @@ export const useAuth = () => {
27
34
  throw new Error("Authentication is not enabled.");
28
35
  }
29
36
  // TODO: Should handle errors/state
30
- await authentication.signOut();
37
+ await authentication.signOut({ navigate });
31
38
 
32
39
  // Redirect to home
33
- window.location.href = "/";
40
+ void navigate("/", { replace: true });
34
41
  },
35
42
 
36
- signup: async () => {
43
+ signup: async (options?: AuthActionOptions) => {
37
44
  if (!isAuthEnabled) {
38
45
  throw new Error("Authentication is not enabled.");
39
46
  }
40
- await authentication.signUp({
41
- redirectTo: window.location.href,
42
- });
47
+ await authentication.signUp(
48
+ { navigate },
49
+ {
50
+ ...options,
51
+ redirectTo: options?.redirectTo ?? window.location.href,
52
+ },
53
+ );
43
54
  },
44
55
  };
45
56
  };
@@ -1,5 +1,6 @@
1
1
  import type { Auth0AuthenticationConfig } from "../../../config/config.js";
2
2
  import type {
3
+ AuthActionContext,
3
4
  AuthenticationPlugin,
4
5
  AuthenticationProviderInitializer,
5
6
  } from "../authentication.js";
@@ -35,7 +36,7 @@ class Auth0AuthenticationProvider
35
36
  }
36
37
  };
37
38
 
38
- signOut = async (): Promise<void> => {
39
+ signOut = async (_: AuthActionContext): Promise<void> => {
39
40
  const as = await this.getAuthServer();
40
41
  const idToken = await this.getAccessToken();
41
42
 
@@ -6,6 +6,7 @@ import { ClientOnly } from "../../components/ClientOnly.js";
6
6
  import { joinUrl } from "../../util/joinUrl.js";
7
7
  import { CoreAuthenticationPlugin } from "../AuthenticationPlugin.js";
8
8
  import type {
9
+ AuthActionContext,
9
10
  AuthenticationPlugin,
10
11
  AuthenticationProviderInitializer,
11
12
  } from "../authentication.js";
@@ -106,7 +107,10 @@ export class AzureB2CAuthPlugin
106
107
  });
107
108
  }
108
109
 
109
- async signUp({ redirectTo }: { redirectTo?: string } = {}) {
110
+ async signUp(
111
+ _: AuthActionContext,
112
+ { redirectTo }: { redirectTo?: string } = {},
113
+ ) {
110
114
  const redirectUri = this.redirectToAfterSignUp ?? redirectTo ?? "/";
111
115
  sessionStorage.setItem("redirect-to", redirectUri);
112
116
 
@@ -116,7 +120,10 @@ export class AzureB2CAuthPlugin
116
120
  });
117
121
  }
118
122
 
119
- async signIn({ redirectTo }: { redirectTo?: string } = {}) {
123
+ async signIn(
124
+ _: AuthActionContext,
125
+ { redirectTo }: { redirectTo?: string } = {},
126
+ ) {
120
127
  const redirectUri = this.redirectToAfterSignIn ?? redirectTo ?? "/";
121
128
  sessionStorage.setItem("redirect-to", redirectUri);
122
129
 
@@ -156,7 +163,7 @@ export class AzureB2CAuthPlugin
156
163
  return request;
157
164
  };
158
165
 
159
- signOut = async () => {
166
+ signOut = async (_: AuthActionContext) => {
160
167
  const account = this.msalInstance.getAllAccounts()[0];
161
168
  if (account) {
162
169
  await this.msalInstance.logoutRedirect({
@@ -3,6 +3,7 @@ import { LogOutIcon } from "lucide-react";
3
3
  import type { ZudokuPlugin } from "zudoku/plugins";
4
4
  import type { ClerkAuthenticationConfig } from "../../../config/config.js";
5
5
  import type {
6
+ AuthActionContext,
6
7
  AuthenticationPlugin,
7
8
  AuthenticationProviderInitializer,
8
9
  } from "../authentication.js";
@@ -147,7 +148,10 @@ const clerkAuth: AuthenticationProviderInitializer<
147
148
  });
148
149
  useAuthState.getState().setLoggedOut();
149
150
  },
150
- signIn: async ({ redirectTo }: { redirectTo?: string } = {}) => {
151
+ signIn: async (
152
+ _: AuthActionContext,
153
+ { redirectTo }: { redirectTo?: string } = {},
154
+ ) => {
151
155
  await ensureLoaded;
152
156
  await clerkApi?.redirectToSignIn({
153
157
  signInForceRedirectUrl: redirectToAfterSignIn
@@ -158,7 +162,10 @@ const clerkAuth: AuthenticationProviderInitializer<
158
162
  : redirectTo,
159
163
  });
160
164
  },
161
- signUp: async ({ redirectTo }: { redirectTo?: string } = {}) => {
165
+ signUp: async (
166
+ _: AuthActionContext,
167
+ { redirectTo }: { redirectTo?: string } = {},
168
+ ) => {
162
169
  await ensureLoaded;
163
170
  await clerkApi?.redirectToSignUp({
164
171
  signInForceRedirectUrl: redirectToAfterSignIn
@@ -1,11 +1,14 @@
1
1
  import logger from "loglevel";
2
2
  import * as oauth from "oauth4webapi";
3
3
  import { ErrorBoundary } from "react-error-boundary";
4
+ import type { NavigateFunction } from "react-router";
4
5
  import type { OpenIDAuthenticationConfig } from "../../../config/config.js";
5
6
  import { ClientOnly } from "../../components/ClientOnly.js";
6
7
  import { joinUrl } from "../../util/joinUrl.js";
7
8
  import { CoreAuthenticationPlugin } from "../AuthenticationPlugin.js";
8
9
  import type {
10
+ AuthActionContext,
11
+ AuthActionOptions,
9
12
  AuthenticationPlugin,
10
13
  AuthenticationProviderInitializer,
11
14
  } from "../authentication.js";
@@ -115,13 +118,16 @@ export class OpenIDAuthenticationProvider
115
118
  });
116
119
  }
117
120
 
118
- async signUp({
119
- redirectTo,
120
- replace = false,
121
- }: {
122
- redirectTo?: string;
123
- replace?: boolean;
124
- } = {}) {
121
+ async signUp(
122
+ _: { navigate: NavigateFunction },
123
+ {
124
+ redirectTo,
125
+ replace = false,
126
+ }: {
127
+ redirectTo?: string;
128
+ replace?: boolean;
129
+ } = {},
130
+ ) {
125
131
  return this.authorize({
126
132
  redirectTo: this.redirectToAfterSignUp ?? redirectTo ?? "/",
127
133
  replace,
@@ -129,13 +135,10 @@ export class OpenIDAuthenticationProvider
129
135
  });
130
136
  }
131
137
 
132
- async signIn({
133
- redirectTo,
134
- replace = false,
135
- }: {
136
- redirectTo?: string;
137
- replace?: boolean;
138
- } = {}) {
138
+ async signIn(
139
+ _: AuthActionContext,
140
+ { redirectTo, replace = false }: AuthActionOptions,
141
+ ) {
139
142
  return this.authorize({
140
143
  redirectTo: this.redirectToAfterSignIn ?? redirectTo ?? "/",
141
144
  replace,
@@ -178,6 +181,7 @@ export class OpenIDAuthenticationProvider
178
181
  const redirectUrl = new URL(window.location.origin);
179
182
  redirectUrl.pathname = this.callbackUrlPath;
180
183
  redirectUrl.search = "";
184
+ redirectUrl.hash = "";
181
185
 
182
186
  authorizationUrl.searchParams.set("client_id", this.client.client_id);
183
187
  authorizationUrl.searchParams.set("redirect_uri", redirectUrl.toString());
@@ -260,7 +264,7 @@ export class OpenIDAuthenticationProvider
260
264
  return request;
261
265
  };
262
266
 
263
- signOut = async () => {
267
+ signOut = async (_: AuthActionContext) => {
264
268
  useAuthState.setState({
265
269
  isAuthenticated: false,
266
270
  isPending: false,
@@ -384,6 +388,7 @@ export class OpenIDAuthenticationProvider
384
388
  const redirectUrl = new URL(url);
385
389
  redirectUrl.pathname = this.callbackUrlPath;
386
390
  redirectUrl.search = "";
391
+ redirectUrl.hash = "";
387
392
 
388
393
  const response = await oauth.authorizationCodeGrantRequest(
389
394
  authServer,
@@ -0,0 +1,75 @@
1
+ import { Auth } from "@supabase/auth-ui-react";
2
+ import {
3
+ ThemeSupa,
4
+ type ThemeVariables,
5
+ type ViewType,
6
+ } from "@supabase/auth-ui-shared";
7
+ import type { SupabaseClient } from "@supabase/supabase-js";
8
+ import { useSearchParams } from "react-router";
9
+ import type { SupabaseAuthenticationConfig } from "../../../../config/config.js";
10
+ import { Heading } from "../../../components/Heading.js";
11
+
12
+ export const SupabaseAuthUI = ({
13
+ client,
14
+ config,
15
+ view = "sign_in",
16
+ }: {
17
+ client: SupabaseClient;
18
+ config: SupabaseAuthenticationConfig;
19
+ view: ViewType;
20
+ }) => {
21
+ const [searchParams] = useSearchParams();
22
+ const redirectTo = searchParams.get("redirectTo");
23
+ const providers = config.provider ? [config.provider] : config.providers;
24
+ const root = config.basePath ?? "/";
25
+ const redirectToAfterSignUp =
26
+ redirectTo ?? config.redirectToAfterSignUp ?? root;
27
+ const redirectToAfterSignIn =
28
+ redirectTo ?? config.redirectToAfterSignIn ?? root;
29
+ const redirectToAfterSignOut =
30
+ redirectTo ?? config.redirectToAfterSignOut ?? root;
31
+
32
+ return (
33
+ <div className="flex items-center justify-center">
34
+ <div className="max-w-md w-full mt-10">
35
+ <Heading level={1}>
36
+ {view === "sign_in" ? "Sign in" : "Sign up"}
37
+ </Heading>
38
+ <Auth
39
+ view={view}
40
+ redirectToAfterSignIn={redirectToAfterSignIn}
41
+ redirectToAfterSignUp={redirectToAfterSignUp}
42
+ redirectToAfterSignOut={redirectToAfterSignOut}
43
+ supabaseClient={client}
44
+ onlyThirdPartyProviders={config.onlyThirdPartyProviders}
45
+ appearance={{
46
+ theme: ThemeSupa,
47
+ variables: {
48
+ default: {
49
+ colors: {
50
+ dividerBackground: "var(--border)",
51
+ brand: "var(--primary)",
52
+ brandAccent: "hsla(from var(--primary) h s l / 0.8)",
53
+ brandButtonText: "var(--primary-foreground)",
54
+ defaultButtonBorder: "var(--border)",
55
+ inputBorder: "var(--border)",
56
+ inputText: "var(--foreground)",
57
+ inputBorderHover: "var(--accent)",
58
+ defaultButtonBackground: "var(--secondary)",
59
+ defaultButtonBackgroundHover: "var(--accent)",
60
+ },
61
+ radii: {
62
+ borderRadiusButton: "var(--radius)",
63
+ buttonBorderRadius: "var(--radius)",
64
+ inputBorderRadius: "var(--radius)",
65
+ },
66
+ } satisfies ThemeVariables,
67
+ },
68
+ }}
69
+ providers={providers}
70
+ redirectTo={config.redirectToAfterSignIn ?? "/"}
71
+ />
72
+ </div>
73
+ </div>
74
+ );
75
+ };
@@ -7,46 +7,38 @@ import {
7
7
  import type { SupabaseAuthenticationConfig } from "../../../config/config.js";
8
8
  import { CoreAuthenticationPlugin } from "../AuthenticationPlugin.js";
9
9
  import type {
10
+ AuthActionContext,
11
+ AuthActionOptions,
10
12
  AuthenticationPlugin,
11
13
  AuthenticationProviderInitializer,
12
14
  } from "../authentication.js";
15
+ import { SignOut } from "../components/SignOut.js";
13
16
  import { AuthorizationError } from "../errors.js";
14
17
  import { type UserProfile, useAuthState } from "../state.js";
18
+ import { SupabaseAuthUI } from "./supabase/SupabaseAuthUI.js";
15
19
 
16
20
  class SupabaseAuthenticationProvider
17
21
  extends CoreAuthenticationPlugin
18
22
  implements AuthenticationPlugin
19
23
  {
20
24
  private readonly client: SupabaseClient;
21
- private readonly provider: Provider;
22
- private readonly redirectToAfterSignUp: string;
23
- private readonly redirectToAfterSignIn: string;
24
- // biome-ignore lint/correctness/noUnusedPrivateClassMembers: Keep around
25
- private readonly redirectToAfterSignOut: string;
26
-
27
- constructor({
28
- supabaseUrl,
29
- supabaseKey,
30
- provider,
31
- redirectToAfterSignUp,
32
- redirectToAfterSignIn,
33
- redirectToAfterSignOut,
34
- basePath,
35
- }: SupabaseAuthenticationConfig) {
25
+ private readonly providers: Provider[];
26
+ private readonly config: SupabaseAuthenticationConfig;
27
+
28
+ constructor(config: SupabaseAuthenticationConfig) {
29
+ const { provider, providers, supabaseUrl, supabaseKey } = config;
36
30
  super();
37
- this.provider = provider;
31
+ this.providers = providers ?? (provider ? [provider] : []);
32
+ if (this.providers.length === 0) {
33
+ throw new Error("At least one provider must be provided");
34
+ }
38
35
  this.client = createClient(supabaseUrl, supabaseKey, {
39
36
  auth: {
40
37
  autoRefreshToken: true,
41
38
  persistSession: true,
42
39
  },
43
40
  });
44
-
45
- const root = basePath ?? "/";
46
-
47
- this.redirectToAfterSignUp = redirectToAfterSignUp ?? root;
48
- this.redirectToAfterSignIn = redirectToAfterSignIn ?? root;
49
- this.redirectToAfterSignOut = redirectToAfterSignOut ?? root;
41
+ this.config = config;
50
42
 
51
43
  this.client.auth.onAuthStateChange(async (event, session) => {
52
44
  if (session && (event === "SIGNED_IN" || event === "TOKEN_REFRESHED")) {
@@ -90,31 +82,55 @@ class SupabaseAuthenticationProvider
90
82
  return request;
91
83
  }
92
84
 
93
- signUp = async ({ redirectTo }: { redirectTo?: string }) => {
94
- const finalRedirectTo = redirectTo ?? this.redirectToAfterSignUp;
85
+ signUp = async (
86
+ { navigate }: AuthActionContext,
87
+ { redirectTo }: AuthActionOptions,
88
+ ) => {
89
+ void navigate(
90
+ redirectTo
91
+ ? `/signup?redirectTo=${encodeURIComponent(redirectTo)}`
92
+ : `/signup`,
93
+ );
94
+ };
95
95
 
96
- // Open Supabase Auth UI in a new window
97
- await this.client.auth.signInWithOAuth({
98
- provider: this.provider,
99
- options: {
100
- redirectTo: window.location.origin + finalRedirectTo,
101
- },
102
- });
96
+ signIn = async (
97
+ { navigate }: AuthActionContext,
98
+ { redirectTo }: AuthActionOptions,
99
+ ) => {
100
+ void navigate(
101
+ redirectTo
102
+ ? `/signin?redirectTo=${encodeURIComponent(redirectTo)}`
103
+ : `/signin`,
104
+ );
103
105
  };
104
106
 
105
- signIn = async ({ redirectTo }: { redirectTo?: string }) => {
106
- const finalRedirectTo = redirectTo ?? this.redirectToAfterSignIn;
107
-
108
- await this.client.auth.signInWithOAuth({
109
- provider: this.provider,
110
- options: {
111
- redirectTo: window.location.origin + finalRedirectTo,
112
- queryParams: {
113
- access_type: "offline",
114
- prompt: "consent",
115
- },
107
+ getRoutes = () => {
108
+ return [
109
+ {
110
+ path: "/signin",
111
+ element: (
112
+ <SupabaseAuthUI
113
+ view="sign_in"
114
+ client={this.client}
115
+ config={this.config}
116
+ />
117
+ ),
116
118
  },
117
- });
119
+ {
120
+ path: "/signup",
121
+ element: (
122
+ <SupabaseAuthUI
123
+ view="sign_up"
124
+ client={this.client}
125
+ config={this.config}
126
+ />
127
+ ),
128
+ },
129
+ {
130
+ path: "/signout",
131
+ element: <SignOut />,
132
+ },
133
+ ];
118
134
  };
119
135
 
120
136
  signOut = async () => {
@@ -1,5 +1,6 @@
1
- import { create, type Mutate, type StoreApi } from "zustand";
1
+ import { create } from "zustand";
2
2
  import { createJSONStorage, persist } from "zustand/middleware";
3
+ import { syncZustandState } from "../util/syncZustandState.js";
3
4
 
4
5
  export interface AuthState<ProviderData = unknown> {
5
6
  isAuthenticated: boolean;
@@ -17,25 +18,6 @@ export interface AuthState<ProviderData = unknown> {
17
18
  }) => void;
18
19
  }
19
20
 
20
- export type StoreWithPersist<T> = Mutate<
21
- StoreApi<T>,
22
- [["zustand/persist", unknown]]
23
- >;
24
-
25
- const withStorageDOMEvents = <T>(store: StoreWithPersist<T>) => {
26
- const storageEventCallback = (e: StorageEvent) => {
27
- if (e.key === store.persist.getOptions().name && e.newValue) {
28
- void store.persist.rehydrate();
29
- }
30
- };
31
-
32
- window.addEventListener("storage", storageEventCallback);
33
-
34
- return () => {
35
- window.removeEventListener("storage", storageEventCallback);
36
- };
37
- };
38
-
39
21
  export const useAuthState = create<AuthState>()(
40
22
  persist(
41
23
  (set) => ({
@@ -85,9 +67,7 @@ export const useAuthState = create<AuthState>()(
85
67
  ),
86
68
  );
87
69
 
88
- if (typeof window !== "undefined") {
89
- withStorageDOMEvents(useAuthState);
90
- }
70
+ syncZustandState(useAuthState);
91
71
 
92
72
  export interface UserProfile {
93
73
  sub: string;
@@ -32,7 +32,7 @@ import { ZudokuProvider } from "./context/ZudokuProvider.js";
32
32
 
33
33
  let zudokuContext: ZudokuContext | undefined;
34
34
 
35
- const ZudokoInner = memo(
35
+ const ZudokuInner = memo(
36
36
  ({ children, ...props }: PropsWithChildren<ZudokuContextOptions>) => {
37
37
  const components = useMemo(
38
38
  () => ({ ...DEFAULT_COMPONENTS, ...props.overrides }),
@@ -105,12 +105,12 @@ const ZudokoInner = memo(
105
105
  },
106
106
  );
107
107
 
108
- ZudokoInner.displayName = "ZudokoInner";
108
+ ZudokuInner.displayName = "ZudokuInner";
109
109
 
110
110
  const Zudoku = (props: ZudokuContextOptions) => {
111
111
  return (
112
112
  <ErrorBoundary FallbackComponent={TopLevelError}>
113
- <ZudokoInner {...props} />
113
+ <ZudokuInner {...props} />
114
114
  </ErrorBoundary>
115
115
  );
116
116
  };
@@ -85,11 +85,11 @@ export const Toc = ({ entries }: { entries: TocEntry[] }) => {
85
85
  <ListTreeIcon size={16} />
86
86
  On this page
87
87
  </div>
88
- <div className="relative ms-2 ps-4">
89
- <div className="absolute inset-0 end-auto bg-border w-[2px]" />
88
+ <div className="relative ms-px ps-4">
89
+ <div className="absolute inset-0 end-auto bg-border w-[1.5px]" />
90
90
  <div
91
91
  className={cn(
92
- "absolute -start-px -translate-y-1 h-6 w-[4px] rounded-sm bg-primary",
92
+ "absolute start-0 -translate-y-1 h-6 w-[2.5px] bg-primary",
93
93
  paintedOnce.current &&
94
94
  "ease-out [transition:top_150ms,opacity_325ms]",
95
95
  )}
@@ -1,11 +1,12 @@
1
- import { useQuery } from "@tanstack/react-query";
2
1
  import { Helmet } from "@zudoku/react-helmet-async";
3
2
  import { use } from "react";
4
3
  import { matchPath, Outlet, useLocation, useNavigate } from "react-router";
4
+ import { Button } from "zudoku/ui/Button.js";
5
5
  import {
6
6
  Dialog,
7
7
  DialogContent,
8
8
  DialogDescription,
9
+ DialogFooter,
9
10
  DialogHeader,
10
11
  DialogTitle,
11
12
  } from "zudoku/ui/Dialog.js";
@@ -37,17 +38,9 @@ export const RouteGuard = () => {
37
38
  const needsToSignIn =
38
39
  isProtectedRoute && !authCheckFn({ auth, context: zudoku });
39
40
 
40
- useQuery({
41
- queryKey: ["login-redirect"],
42
- queryFn: async () => {
43
- await new Promise((resolve) => setTimeout(resolve, 1200));
44
- await zudoku.authentication?.signIn({
45
- redirectTo: latestPath.current,
46
- });
47
- return true;
48
- },
49
- enabled: typeof window !== "undefined" && needsToSignIn && !auth.isPending,
50
- });
41
+ if (needsToSignIn && auth.isPending && typeof window !== "undefined") {
42
+ return null;
43
+ }
51
44
 
52
45
  if (needsToSignIn) {
53
46
  return (
@@ -61,11 +54,38 @@ export const RouteGuard = () => {
61
54
  >
62
55
  <DialogContent>
63
56
  <DialogHeader>
64
- <DialogTitle>Logging you in...</DialogTitle>
57
+ <DialogTitle>Login to continue</DialogTitle>
65
58
  </DialogHeader>
66
59
  <DialogDescription>
67
60
  Please wait while we log you in.
68
61
  </DialogDescription>
62
+ <DialogFooter>
63
+ <Button variant="outline" onClick={() => void navigate(-1)}>
64
+ Cancel
65
+ </Button>
66
+ <div className="w-full"></div>
67
+ <Button
68
+ variant="secondary"
69
+ onClick={() =>
70
+ void zudoku.authentication?.signUp(
71
+ { navigate },
72
+ { redirectTo: latestPath.current },
73
+ )
74
+ }
75
+ >
76
+ Register
77
+ </Button>
78
+ <Button
79
+ onClick={() =>
80
+ void zudoku.authentication?.signIn(
81
+ { navigate },
82
+ { redirectTo: latestPath.current },
83
+ )
84
+ }
85
+ >
86
+ Login{" "}
87
+ </Button>
88
+ </DialogFooter>
69
89
  </DialogContent>
70
90
  </Dialog>
71
91
  );
@@ -12,7 +12,7 @@ export const CollapsibleHeaderTrigger = ({
12
12
  return (
13
13
  <div
14
14
  className={cn(
15
- "grid grid-cols-[max-content_1fr_min-content_max-content] items-center gap-4 group bg-muted w-full h-10 ps-4 pe-2 border-b",
15
+ "grid grid-cols-[max-content_1fr_min-content_max-content] items-center gap-2 group bg-muted w-full h-10 ps-4 pe-2 border-b",
16
16
  className,
17
17
  )}
18
18
  >
@@ -24,7 +24,7 @@ export const CollapsibleHeaderTrigger = ({
24
24
  )}
25
25
  >
26
26
  <ChevronUpIcon
27
- className="group-data-[state=open]:rotate-180 transition-transform flex-shrink-0"
27
+ className="group-data-[state=open]:rotate-180 transition-transform shrink-0"
28
28
  size={16}
29
29
  />
30
30
  </CollapsibleTrigger>
@@ -145,6 +145,7 @@ export const Playground = ({
145
145
  const { setRememberedIdentity, getRememberedIdentity } = useIdentityStore();
146
146
  const [, startTransition] = useTransition();
147
147
  const { skipLogin, setSkipLogin } = useRememberSkipLoginDialog();
148
+ const [isLoginDialogDismissed, setIsLoginDialogDismissed] = useState(false);
148
149
  const [showLongRunningWarning, setShowLongRunningWarning] = useState(false);
149
150
  const abortControllerRef = useRef<AbortController | undefined>(undefined);
150
151
  const latestSetRememberedIdentity = useLatest(setRememberedIdentity);
@@ -361,7 +362,7 @@ export const Playground = ({
361
362
  </div>
362
363
  );
363
364
 
364
- const showLogin = requiresLogin && !skipLogin;
365
+ const showLogin = requiresLogin && !skipLogin && !isLoginDialogDismissed;
365
366
  const isBodySupported = ["POST", "PUT", "PATCH", "DELETE"].includes(
366
367
  method.toUpperCase(),
367
368
  );
@@ -397,7 +398,17 @@ export const Playground = ({
397
398
  />
398
399
  <RequestLoginDialog
399
400
  open={showLogin}
400
- setOpen={(open) => setSkipLogin(!open)}
401
+ setOpen={(open) => {
402
+ if (!open) {
403
+ setIsLoginDialogDismissed(true);
404
+ }
405
+ }}
406
+ onSkip={(rememberSkip) => {
407
+ setIsLoginDialogDismissed(true);
408
+ if (rememberSkip) {
409
+ setSkipLogin(true);
410
+ }
411
+ }}
401
412
  onSignUp={onSignUp}
402
413
  onLogin={onLogin}
403
414
  />