zudoku 0.66.1 → 0.66.3

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 (168) hide show
  1. package/dist/config/validators/validate.d.ts +32 -3
  2. package/dist/config/validators/validate.js +1 -1
  3. package/dist/config/validators/validate.js.map +1 -1
  4. package/dist/flat-config.d.ts +1 -1
  5. package/dist/lib/auth/issuer.js +1 -1
  6. package/dist/lib/auth/issuer.js.map +1 -1
  7. package/dist/lib/authentication/authentication.d.ts +3 -2
  8. package/dist/lib/authentication/hook.d.ts +2 -0
  9. package/dist/lib/authentication/hook.js +10 -0
  10. package/dist/lib/authentication/hook.js.map +1 -1
  11. package/dist/lib/authentication/providers/firebase.js +67 -9
  12. package/dist/lib/authentication/providers/firebase.js.map +1 -1
  13. package/dist/lib/authentication/ui/EmailVerificationUi.d.ts +4 -0
  14. package/dist/lib/authentication/ui/EmailVerificationUi.js +34 -0
  15. package/dist/lib/authentication/ui/EmailVerificationUi.js.map +1 -0
  16. package/dist/lib/authentication/ui/ZudokuAuthUi.d.ts +7 -2
  17. package/dist/lib/authentication/ui/ZudokuAuthUi.js +43 -11
  18. package/dist/lib/authentication/ui/ZudokuAuthUi.js.map +1 -1
  19. package/dist/lib/authentication/utils/relativeRedirectUrl.d.ts +1 -0
  20. package/dist/lib/authentication/utils/relativeRedirectUrl.js +8 -0
  21. package/dist/lib/authentication/utils/relativeRedirectUrl.js.map +1 -0
  22. package/dist/lib/components/index.d.ts +2 -0
  23. package/dist/lib/errors/ErrorMessage.d.ts +3 -0
  24. package/dist/lib/errors/ErrorMessage.js +16 -0
  25. package/dist/lib/errors/ErrorMessage.js.map +1 -0
  26. package/dist/lib/hooks/index.d.ts +2 -0
  27. package/dist/lib/plugins/api-keys/SettingsApiKeys.js +9 -172
  28. package/dist/lib/plugins/api-keys/SettingsApiKeys.js.map +1 -1
  29. package/dist/lib/plugins/api-keys/index.d.ts +4 -1
  30. package/dist/lib/plugins/api-keys/index.js +19 -14
  31. package/dist/lib/plugins/api-keys/index.js.map +1 -1
  32. package/dist/lib/plugins/api-keys/settings/ApiKeyItem.d.ts +12 -0
  33. package/dist/lib/plugins/api-keys/settings/ApiKeyItem.js +133 -0
  34. package/dist/lib/plugins/api-keys/settings/ApiKeyItem.js.map +1 -0
  35. package/dist/lib/plugins/api-keys/settings/ApiKeyList.d.ts +4 -0
  36. package/dist/lib/plugins/api-keys/settings/ApiKeyList.js +30 -0
  37. package/dist/lib/plugins/api-keys/settings/ApiKeyList.js.map +1 -0
  38. package/dist/lib/plugins/api-keys/settings/RevealApiKey.d.ts +6 -0
  39. package/dist/lib/plugins/api-keys/settings/RevealApiKey.js +39 -0
  40. package/dist/lib/plugins/api-keys/settings/RevealApiKey.js.map +1 -0
  41. package/dist/vite/config.js +8 -4
  42. package/dist/vite/config.js.map +1 -1
  43. package/dist/vite/plugin-api-keys.js +4 -1
  44. package/dist/vite/plugin-api-keys.js.map +1 -1
  45. package/dist/vite/zuplo.d.ts +13 -0
  46. package/dist/vite/zuplo.js +15 -0
  47. package/dist/vite/zuplo.js.map +1 -0
  48. package/lib/{ClaudeLogo-B4Xxt-x_.js → ClaudeLogo-BZslN9XF.js} +3 -3
  49. package/lib/{ClaudeLogo-B4Xxt-x_.js.map → ClaudeLogo-BZslN9XF.js.map} +1 -1
  50. package/lib/{Drawer-Ci7XwhqT.js → Drawer-BRMcpfuF.js} +6 -6
  51. package/lib/{Drawer-Ci7XwhqT.js.map → Drawer-BRMcpfuF.js.map} +1 -1
  52. package/lib/Frame-DxlznfVd.js +205 -0
  53. package/lib/Frame-DxlznfVd.js.map +1 -0
  54. package/lib/{useMutation-C6RqWmTS.js → Input-D-kqEQ5M.js} +41 -23
  55. package/lib/Input-D-kqEQ5M.js.map +1 -0
  56. package/lib/{MdxPage-BagO2c-n.js → MdxPage-B4zZq5aR.js} +8 -8
  57. package/lib/{MdxPage-BagO2c-n.js.map → MdxPage-B4zZq5aR.js.map} +1 -1
  58. package/lib/{Mermaid-D_VSX7_Q.js → Mermaid-BjSczjLW.js} +3 -3
  59. package/lib/{Mermaid-D_VSX7_Q.js.map → Mermaid-BjSczjLW.js.map} +1 -1
  60. package/lib/{OAuthErrorPage-Fq54RLgt.js → OAuthErrorPage-DRY2hlga.js} +20 -21
  61. package/lib/{OAuthErrorPage-Fq54RLgt.js.map → OAuthErrorPage-DRY2hlga.js.map} +1 -1
  62. package/lib/{OasProvider-DPH8mwDa.js → OasProvider-lMwTD76Y.js} +4 -4
  63. package/lib/{OasProvider-DPH8mwDa.js.map → OasProvider-lMwTD76Y.js.map} +1 -1
  64. package/lib/{OperationList-C0jiEaG5.js → OperationList-Bm76b4vl.js} +23 -24
  65. package/lib/{OperationList-C0jiEaG5.js.map → OperationList-Bm76b4vl.js.map} +1 -1
  66. package/lib/{RouteGuard-9wjejsKm.js → RouteGuard-DGc32XJV.js} +4 -4
  67. package/lib/{RouteGuard-9wjejsKm.js.map → RouteGuard-DGc32XJV.js.map} +1 -1
  68. package/lib/{SchemaList-BU0zCHn9.js → SchemaList-DX4FPogg.js} +7 -7
  69. package/lib/{SchemaList-BU0zCHn9.js.map → SchemaList-DX4FPogg.js.map} +1 -1
  70. package/lib/SchemaView-CjXvSRxy.js +434 -0
  71. package/lib/SchemaView-CjXvSRxy.js.map +1 -0
  72. package/lib/{Select-CkxXP5I7.js → Secret-BxGpIhDP.js} +121 -121
  73. package/lib/Secret-BxGpIhDP.js.map +1 -0
  74. package/lib/{SignUp-BjS4ozA7.js → SignUp-CntxjFGS.js} +4 -4
  75. package/lib/{SignUp-BjS4ozA7.js.map → SignUp-CntxjFGS.js.map} +1 -1
  76. package/lib/{SyntaxHighlight-Kdyskw3C.js → SyntaxHighlight-Dgd0AaaX.js} +1508 -1492
  77. package/lib/SyntaxHighlight-Dgd0AaaX.js.map +1 -0
  78. package/lib/{Toc-DJxFPfcS.js → Toc-L1vGGHA1.js} +2 -2
  79. package/lib/{Toc-DJxFPfcS.js.map → Toc-L1vGGHA1.js.map} +1 -1
  80. package/lib/{ZudokuContext-BXldanA8.js → ZudokuContext-DNHMZfcP.js} +33 -33
  81. package/lib/{ZudokuContext-BXldanA8.js.map → ZudokuContext-DNHMZfcP.js.map} +1 -1
  82. package/lib/{chunk-PVWAREVJ-dLIqswPy.js → chunk-PVWAREVJ-ClM0m2aJ.js} +19 -19
  83. package/lib/{chunk-PVWAREVJ-dLIqswPy.js.map → chunk-PVWAREVJ-ClM0m2aJ.js.map} +1 -1
  84. package/lib/{circular-CzWF1hj5.js → circular-BIN_WQ0c.js} +2 -2
  85. package/lib/{circular-CzWF1hj5.js.map → circular-BIN_WQ0c.js.map} +1 -1
  86. package/lib/{createServer-BIr2_tGn.js → createServer-BEl12QFw.js} +4 -4
  87. package/lib/{createServer-BIr2_tGn.js.map → createServer-BEl12QFw.js.map} +1 -1
  88. package/lib/createVariantComponent-CQVt-H3r.js +18 -0
  89. package/lib/createVariantComponent-CQVt-H3r.js.map +1 -0
  90. package/lib/{errors-Bs4duWDy.js → errors-CtBbD47A.js} +2 -2
  91. package/lib/{errors-Bs4duWDy.js.map → errors-CtBbD47A.js.map} +1 -1
  92. package/lib/{firebase-qUdSEL1p.js → firebase-CT38ugg4.js} +1588 -1342
  93. package/lib/firebase-CT38ugg4.js.map +1 -0
  94. package/lib/hook-CHw_R_xu.js +52 -0
  95. package/lib/hook-CHw_R_xu.js.map +1 -0
  96. package/lib/{ErrorAlert-DrOR8w3f.js → index-CG2v-T7-.js} +5373 -4335
  97. package/lib/index-CG2v-T7-.js.map +1 -0
  98. package/lib/{index-Bh-MffiL.js → index-CISwSpdT.js} +2 -2
  99. package/lib/{index-Bh-MffiL.js.map → index-CISwSpdT.js.map} +1 -1
  100. package/lib/index-CrcNWbel.js.map +1 -1
  101. package/lib/{index-Css56y3F.js → index-DXXZDuSJ.js} +4 -4
  102. package/lib/{index-Css56y3F.js.map → index-DXXZDuSJ.js.map} +1 -1
  103. package/lib/{index-0oT9beQN.js → index-jI2Fjpy-.js} +98 -99
  104. package/lib/{index-0oT9beQN.js.map → index-jI2Fjpy-.js.map} +1 -1
  105. package/lib/index.esm-BYObtETB.js.map +1 -1
  106. package/lib/{index.esm-kW-Utcsi.js → index.esm-Bu35TNgg.js} +16 -14
  107. package/lib/index.esm-Bu35TNgg.js.map +1 -0
  108. package/lib/index.esm-DtzT_KoE.js.map +1 -1
  109. package/lib/jsx-runtime-BzflLqGi.js.map +1 -1
  110. package/lib/{mutation-BoVlx8yA.js → mutation-DMHWqmFp.js} +2 -2
  111. package/lib/{mutation-BoVlx8yA.js.map → mutation-DMHWqmFp.js.map} +1 -1
  112. package/lib/ui/Carousel.js.map +1 -1
  113. package/lib/ui/Drawer.js +2 -2
  114. package/lib/ui/SyntaxHighlight.js +2 -2
  115. package/lib/zudoku.__internal.js +507 -479
  116. package/lib/zudoku.__internal.js.map +1 -1
  117. package/lib/zudoku.auth-auth0.js +1 -1
  118. package/lib/zudoku.auth-azureb2c.js +4 -4
  119. package/lib/zudoku.auth-clerk.js +2 -2
  120. package/lib/zudoku.auth-firebase.js +6 -5
  121. package/lib/zudoku.auth-firebase.js.map +1 -1
  122. package/lib/zudoku.auth-openid.js +4 -4
  123. package/lib/zudoku.auth-supabase.js +5 -5
  124. package/lib/zudoku.components.js +20 -21
  125. package/lib/zudoku.components.js.map +1 -1
  126. package/lib/zudoku.hooks.js +3 -3
  127. package/lib/zudoku.mermaid.js +3 -3
  128. package/lib/zudoku.plugin-api-catalog.js +8 -9
  129. package/lib/zudoku.plugin-api-catalog.js.map +1 -1
  130. package/lib/zudoku.plugin-api-keys.js +580 -543
  131. package/lib/zudoku.plugin-api-keys.js.map +1 -1
  132. package/lib/zudoku.plugin-custom-pages.js +1 -1
  133. package/lib/zudoku.plugin-markdown.js +1 -1
  134. package/lib/zudoku.plugin-openapi.js +3 -3
  135. package/lib/zudoku.plugin-redirect.js +1 -1
  136. package/lib/zudoku.plugin-search-pagefind.js +3 -3
  137. package/lib/zudoku.router.js +2 -2
  138. package/lib/zudoku.router.js.map +1 -1
  139. package/package.json +18 -16
  140. package/src/lib/auth/issuer.ts +1 -1
  141. package/src/lib/authentication/authentication.ts +8 -2
  142. package/src/lib/authentication/hook.ts +16 -0
  143. package/src/lib/authentication/providers/firebase.tsx +98 -6
  144. package/src/lib/authentication/ui/EmailVerificationUi.tsx +129 -0
  145. package/src/lib/authentication/ui/ZudokuAuthUi.tsx +170 -38
  146. package/src/lib/authentication/utils/relativeRedirectUrl.ts +12 -0
  147. package/src/lib/errors/ErrorMessage.tsx +38 -0
  148. package/src/lib/plugins/api-keys/SettingsApiKeys.tsx +36 -476
  149. package/src/lib/plugins/api-keys/index.tsx +33 -18
  150. package/src/lib/plugins/api-keys/settings/ApiKeyItem.tsx +342 -0
  151. package/src/lib/plugins/api-keys/settings/ApiKeyList.tsx +64 -0
  152. package/src/lib/plugins/api-keys/settings/RevealApiKey.tsx +124 -0
  153. package/lib/ErrorAlert-DrOR8w3f.js.map +0 -1
  154. package/lib/RouterError-DSLXagd5.js +0 -42
  155. package/lib/RouterError-DSLXagd5.js.map +0 -1
  156. package/lib/SchemaView-DVae4RO2.js +0 -597
  157. package/lib/SchemaView-DVae4RO2.js.map +0 -1
  158. package/lib/Select-CkxXP5I7.js.map +0 -1
  159. package/lib/SyntaxHighlight-Kdyskw3C.js.map +0 -1
  160. package/lib/createVariantComponent-B9_dVBvu.js +0 -35
  161. package/lib/createVariantComponent-B9_dVBvu.js.map +0 -1
  162. package/lib/firebase-qUdSEL1p.js.map +0 -1
  163. package/lib/hook-BNxidGQq.js +0 -40
  164. package/lib/hook-BNxidGQq.js.map +0 -1
  165. package/lib/index-CCmMJp02.js +0 -1059
  166. package/lib/index-CCmMJp02.js.map +0 -1
  167. package/lib/index.esm-kW-Utcsi.js.map +0 -1
  168. package/lib/useMutation-C6RqWmTS.js.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zudoku",
3
- "version": "0.66.1",
3
+ "version": "0.66.3",
4
4
  "type": "module",
5
5
  "homepage": "https://zudoku.dev",
6
6
  "repository": {
@@ -180,11 +180,11 @@
180
180
  "@radix-ui/react-visually-hidden": "1.2.4",
181
181
  "@scalar/openapi-parser": "0.23.2",
182
182
  "@sentry/node": "10.27.0",
183
- "@shikijs/engine-javascript": "3.17.0",
184
- "@shikijs/langs": "3.17.0",
185
- "@shikijs/rehype": "3.17.0",
186
- "@shikijs/themes": "3.17.0",
187
- "@shikijs/transformers": "3.17.0",
183
+ "@shikijs/engine-javascript": "3.19.0",
184
+ "@shikijs/langs": "3.19.0",
185
+ "@shikijs/rehype": "3.19.0",
186
+ "@shikijs/themes": "3.19.0",
187
+ "@shikijs/transformers": "3.19.0",
188
188
  "@sindresorhus/slugify": "3.0.0",
189
189
  "@stefanprobst/rehype-extract-toc": "3.0.0",
190
190
  "@tailwindcss/typography": "0.5.19",
@@ -192,18 +192,20 @@
192
192
  "@tanem/react-nprogress": "5.0.56",
193
193
  "@tanstack/react-query": "5.85.5",
194
194
  "@types/react": "19.2.7",
195
- "@types/react-dom": "19.2.0",
195
+ "@types/react-dom": "19.2.3",
196
196
  "@vitejs/plugin-react": "5.1.0",
197
197
  "@x0k/json-schema-merge": "1.0.2",
198
198
  "@zudoku/httpsnippet": "10.0.9",
199
199
  "@zudoku/react-helmet-async": "2.0.5",
200
200
  "@zuplo/mcp": "^0.0.22",
201
+ "bs58": "^6.0.0",
201
202
  "class-variance-authority": "0.7.1",
202
203
  "clsx": "2.1.1",
203
204
  "cmdk": "1.1.1",
205
+ "dotenv": "^17.2.3",
204
206
  "embla-carousel-react": "8.6.0",
205
207
  "estree-util-value-to-estree": "3.4.1",
206
- "express": "5.2.0",
208
+ "express": "5.2.1",
207
209
  "fast-equals": "5.2.2",
208
210
  "glob": "13.0.0",
209
211
  "glob-parent": "6.0.2",
@@ -229,7 +231,7 @@
229
231
  "posthog-node": "5.14.1",
230
232
  "react-error-boundary": "6.0.0",
231
233
  "react-hook-form": "7.66.0",
232
- "react-is": "19.2.0",
234
+ "react-is": "19.2.1",
233
235
  "react-markdown": "10.1.0",
234
236
  "react-router": "7.8.2",
235
237
  "rehype-mdx-import-media": "1.2.0",
@@ -243,11 +245,11 @@
243
245
  "remark-mdx-frontmatter": "5.2.0",
244
246
  "rollup": "4.52.5",
245
247
  "semver": "7.7.2",
246
- "shiki": "3.17.0",
248
+ "shiki": "3.19.0",
247
249
  "sitemap": "9.0.0",
248
250
  "strip-ansi": "7.1.2",
249
251
  "tailwind-merge": "3.3.1",
250
- "tailwindcss": "4.1.16",
252
+ "tailwindcss": "4.1.17",
251
253
  "tw-animate-css": "1.4.0",
252
254
  "unified": "^11.0.5",
253
255
  "unist-util-visit": "5.0.0",
@@ -266,14 +268,14 @@
266
268
  "@testing-library/jest-dom": "6.9.1",
267
269
  "@testing-library/react": "16.3.0",
268
270
  "@types/estree": "1.0.8",
269
- "@types/express": "5.0.5",
271
+ "@types/express": "5.0.6",
270
272
  "@types/glob-parent": "5.1.3",
271
273
  "@types/har-format": "1.2.16",
272
274
  "@types/hast": "^3.0.4",
273
275
  "@types/json-schema": "7.0.15",
274
276
  "@types/mdast": "4.0.4",
275
277
  "@types/mdx": "2.0.13",
276
- "@types/node": "22.13.5",
278
+ "@types/node": "22.19.1",
277
279
  "@types/react-is": "19.2.0",
278
280
  "@types/semver": "7.7.0",
279
281
  "@types/unist": "^3.0.3",
@@ -282,12 +284,12 @@
282
284
  "esbuild": "0.27.0",
283
285
  "happy-dom": "20.0.10",
284
286
  "mdast-util-mdx": "3.0.0",
285
- "react": "19.2.0",
286
- "react-dom": "19.2.0",
287
+ "react": "19.2.1",
288
+ "react-dom": "19.2.1",
287
289
  "rollup-plugin-visualizer": "6.0.5",
288
290
  "tsx": "4.20.6",
289
291
  "typescript": "5.9.3",
290
- "vitest": "4.0.6"
292
+ "vitest": "4.0.15"
291
293
  },
292
294
  "peerDependencies": {
293
295
  "@azure/msal-browser": "^4.13.0",
@@ -32,7 +32,7 @@ export const getIssuer = async (config: ZudokuConfig) => {
32
32
  return config.authentication.issuer;
33
33
  }
34
34
  case "firebase": {
35
- return config.authentication.authDomain;
35
+ return `https://securetoken.google.com/${config.authentication.projectId}`;
36
36
  }
37
37
  case undefined: {
38
38
  return undefined;
@@ -6,6 +6,7 @@ export type AuthActionOptions = { redirectTo?: string; replace?: boolean };
6
6
 
7
7
  export interface AuthenticationPlugin {
8
8
  initialize?(context: ZudokuContext): Promise<void>;
9
+ onPageLoad?(): void;
9
10
  setNavigate?(navigate: NavigateFunction): void;
10
11
 
11
12
  signUp(
@@ -18,12 +19,17 @@ export interface AuthenticationPlugin {
18
19
  ): Promise<void>;
19
20
 
20
21
  signOut({ navigate }: AuthActionContext): Promise<void>;
22
+
23
+ signRequest(request: Request): Promise<Request>;
24
+ requestEmailVerification?(
25
+ { navigate }: AuthActionContext,
26
+ options?: AuthActionOptions,
27
+ ): Promise<void>;
28
+
21
29
  /**
22
30
  * @deprecated use signRequest instead
23
31
  */
24
32
  getAccessToken?(): Promise<string>;
25
- onPageLoad?(): void;
26
- signRequest(request: Request): Promise<Request>;
27
33
  }
28
34
 
29
35
  export type AuthenticationProviderInitializer<TConfig> = (
@@ -52,5 +52,21 @@ export const useAuth = () => {
52
52
  },
53
53
  );
54
54
  },
55
+ supportsEmailVerification:
56
+ typeof authentication?.requestEmailVerification === "function",
57
+
58
+ requestEmailVerification: async (options?: AuthActionOptions) => {
59
+ if (!isAuthEnabled) {
60
+ throw new Error("Authentication is not enabled.");
61
+ }
62
+
63
+ await authentication.requestEmailVerification?.(
64
+ { navigate },
65
+ {
66
+ ...options,
67
+ redirectTo: options?.redirectTo ?? window.location.href,
68
+ },
69
+ );
70
+ },
55
71
  };
56
72
  };
@@ -3,12 +3,15 @@ import {
3
3
  type Auth,
4
4
  createUserWithEmailAndPassword,
5
5
  getAuth,
6
+ sendEmailVerification,
7
+ sendPasswordResetEmail,
6
8
  signInWithEmailAndPassword,
7
9
  signInWithPopup,
8
10
  signOut,
9
11
  type User,
10
12
  } from "firebase/auth";
11
13
  import type { FirebaseAuthenticationConfig } from "../../../config/config.js";
14
+ import { ZudokuError } from "../../util/invariant.js";
12
15
  import { CoreAuthenticationPlugin } from "../AuthenticationPlugin.js";
13
16
  import type {
14
17
  AuthActionContext,
@@ -19,7 +22,12 @@ import type {
19
22
  import { SignOut } from "../components/SignOut.js";
20
23
  import { AuthorizationError } from "../errors.js";
21
24
  import { useAuthState } from "../state.js";
22
- import { ZudokuSignInUi, ZudokuSignUpUi } from "../ui/ZudokuAuthUi.js";
25
+ import { EmailVerificationUi } from "../ui/EmailVerificationUi.js";
26
+ import {
27
+ ZudokuPasswordResetUi,
28
+ ZudokuSignInUi,
29
+ ZudokuSignUpUi,
30
+ } from "../ui/ZudokuAuthUi.js";
23
31
 
24
32
  class FirebaseAuthenticationProvider
25
33
  extends CoreAuthenticationPlugin
@@ -28,6 +36,7 @@ class FirebaseAuthenticationProvider
28
36
  private readonly app: FirebaseApp;
29
37
  private readonly auth: Auth;
30
38
  private readonly providers: string[];
39
+ private readonly enableUsernamePassword: boolean;
31
40
 
32
41
  constructor(config: FirebaseAuthenticationConfig) {
33
42
  super();
@@ -42,7 +51,13 @@ class FirebaseAuthenticationProvider
42
51
  measurementId: config.measurementId,
43
52
  });
44
53
  this.auth = getAuth(this.app);
45
- this.providers = config.providers ?? [];
54
+ this.providers = config.providers?.filter((p) => p !== "password") ?? [];
55
+ this.enableUsernamePassword =
56
+ config.providers?.includes("password") ?? false;
57
+ }
58
+
59
+ async initialize() {
60
+ await this.auth.authStateReady();
46
61
  }
47
62
 
48
63
  async signRequest(request: Request): Promise<Request> {
@@ -76,13 +91,77 @@ class FirebaseAuthenticationProvider
76
91
  );
77
92
  };
78
93
 
94
+ requestEmailVerification = async (
95
+ { navigate }: AuthActionContext,
96
+ { redirectTo }: AuthActionOptions,
97
+ ) => {
98
+ if (!this.auth.currentUser) {
99
+ throw new ZudokuError("User is not authenticated", {
100
+ title: "User not authenticated",
101
+ });
102
+ }
103
+
104
+ await sendEmailVerification(this.auth.currentUser);
105
+ void navigate(
106
+ redirectTo
107
+ ? `/verify-email?redirectTo=${encodeURIComponent(redirectTo)}`
108
+ : `/verify-email`,
109
+ );
110
+ };
111
+
79
112
  getRoutes = () => {
80
113
  return [
114
+ {
115
+ path: "/verify-email",
116
+ element: (
117
+ <EmailVerificationUi
118
+ onResendVerification={async () => {
119
+ if (!this.auth.currentUser) {
120
+ throw new ZudokuError("User is not authenticated", {
121
+ title: "User not authenticated",
122
+ });
123
+ }
124
+ await sendEmailVerification(this.auth.currentUser);
125
+ }}
126
+ onCheckVerification={async () => {
127
+ if (!this.auth.currentUser) {
128
+ throw new ZudokuError("User is not authenticated", {
129
+ title: "User not authenticated",
130
+ });
131
+ }
132
+ await this.auth.currentUser.reload();
133
+ const isVerified = this.auth.currentUser.emailVerified;
134
+
135
+ if (isVerified) {
136
+ await this.auth.currentUser.getIdToken(true);
137
+ await this.setUserLoggedIn(this.auth.currentUser);
138
+ }
139
+
140
+ return isVerified;
141
+ }}
142
+ />
143
+ ),
144
+ },
145
+ {
146
+ path: "/reset-password",
147
+ element: (
148
+ <ZudokuPasswordResetUi
149
+ onPasswordReset={async (email: string) => {
150
+ try {
151
+ await sendPasswordResetEmail(this.auth, email);
152
+ } catch (error) {
153
+ throw Error(getFirebaseErrorMessage(error), { cause: error });
154
+ }
155
+ }}
156
+ />
157
+ ),
158
+ },
81
159
  {
82
160
  path: "/signin",
83
161
  element: (
84
162
  <ZudokuSignInUi
85
163
  providers={this.providers}
164
+ enableUsernamePassword={this.enableUsernamePassword}
86
165
  onOAuthSignIn={async (providerId: string) => {
87
166
  useAuthState.setState({ isPending: true });
88
167
  const provider = await getProviderForId(providerId);
@@ -109,7 +188,13 @@ class FirebaseAuthenticationProvider
109
188
  password: string,
110
189
  ) => {
111
190
  try {
112
- await signInWithEmailAndPassword(this.auth, email, password);
191
+ useAuthState.setState({ isPending: false });
192
+ const result = await signInWithEmailAndPassword(
193
+ this.auth,
194
+ email,
195
+ password,
196
+ );
197
+ await this.setUserLoggedIn(result.user);
113
198
  } catch (error) {
114
199
  throw Error(getFirebaseErrorMessage(error), { cause: error });
115
200
  }
@@ -122,6 +207,7 @@ class FirebaseAuthenticationProvider
122
207
  element: (
123
208
  <ZudokuSignUpUi
124
209
  providers={this.providers}
210
+ enableUsernamePassword={this.enableUsernamePassword}
125
211
  onOAuthSignUp={async (providerId: string) => {
126
212
  const provider = await getProviderForId(providerId);
127
213
  if (!provider) {
@@ -135,7 +221,13 @@ class FirebaseAuthenticationProvider
135
221
  email: string,
136
222
  password: string,
137
223
  ) => {
138
- await createUserWithEmailAndPassword(this.auth, email, password);
224
+ useAuthState.setState({ isPending: true });
225
+ const createUser = await createUserWithEmailAndPassword(
226
+ this.auth,
227
+ email,
228
+ password,
229
+ );
230
+ await this.setUserLoggedIn(createUser.user);
139
231
  }}
140
232
  />
141
233
  ),
@@ -162,13 +254,13 @@ class FirebaseAuthenticationProvider
162
254
  const user = this.auth.currentUser;
163
255
 
164
256
  if (user) {
165
- await this.updateUserState(user);
257
+ await this.setUserLoggedIn(user);
166
258
  } else {
167
259
  useAuthState.setState({ isPending: false });
168
260
  }
169
261
  };
170
262
 
171
- private async updateUserState(user: User) {
263
+ private async setUserLoggedIn(user: User) {
172
264
  useAuthState.getState().setLoggedIn({
173
265
  profile: {
174
266
  sub: user.uid,
@@ -0,0 +1,129 @@
1
+ import { useMutation, useQuery } from "@tanstack/react-query";
2
+ import { CheckIcon, MailCheck, RefreshCw } from "lucide-react";
3
+ import { Navigate, useSearchParams } from "react-router";
4
+ import { ActionButton } from "zudoku/ui/ActionButton.js";
5
+ import { Alert, AlertDescription, AlertTitle } from "zudoku/ui/Alert.js";
6
+ import { Button } from "zudoku/ui/Button.js";
7
+ import {
8
+ Card,
9
+ CardContent,
10
+ CardDescription,
11
+ CardHeader,
12
+ CardTitle,
13
+ } from "zudoku/ui/Card.js";
14
+ import createVariantComponent from "../../util/createVariantComponent.js";
15
+ import { getRelativeRedirectUrl } from "../utils/relativeRedirectUrl.js";
16
+
17
+ export const EmailVerificationUi = ({
18
+ onResendVerification,
19
+ onCheckVerification,
20
+ }: {
21
+ onResendVerification: () => Promise<void>;
22
+ onCheckVerification: () => Promise<boolean>;
23
+ }) => {
24
+ const [searchParams] = useSearchParams();
25
+ const redirectTo = searchParams.get("redirectTo");
26
+ const relativeRedirectTo = getRelativeRedirectUrl(redirectTo);
27
+
28
+ const resendMutation = useMutation({
29
+ mutationFn: async () => {
30
+ await onResendVerification();
31
+ },
32
+ });
33
+
34
+ const checkVerificationMutation = useQuery({
35
+ queryKey: ["check-verification"],
36
+ queryFn: async () => {
37
+ const isVerified = await onCheckVerification();
38
+ return isVerified;
39
+ },
40
+ });
41
+
42
+ const error = resendMutation.error ?? checkVerificationMutation.error ?? null;
43
+
44
+ return (
45
+ <AuthCard>
46
+ {checkVerificationMutation.data === true && (
47
+ <Navigate to={relativeRedirectTo} />
48
+ )}
49
+ <CardHeader className="text-center">
50
+ <div className="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-primary/10">
51
+ <MailCheck className="h-8 w-8 text-primary" />
52
+ </div>
53
+ <CardTitle>Verify your email</CardTitle>
54
+ <CardDescription>We've sent a verification link</CardDescription>
55
+ </CardHeader>
56
+ <CardContent className="flex flex-col gap-4">
57
+ {error && (
58
+ <Alert variant="destructive">
59
+ <AlertTitle>Error</AlertTitle>
60
+ <AlertDescription>{error?.message}</AlertDescription>
61
+ </Alert>
62
+ )}
63
+
64
+ {resendMutation.isSuccess && (
65
+ <Alert>
66
+ <AlertTitle>Email sent</AlertTitle>
67
+ <AlertDescription>
68
+ A new verification email has been sent. Please check your inbox.
69
+ </AlertDescription>
70
+ </Alert>
71
+ )}
72
+
73
+ {checkVerificationMutation.isSuccess &&
74
+ !checkVerificationMutation.data && (
75
+ <Alert>
76
+ <AlertDescription>
77
+ {checkVerificationMutation.isFetching
78
+ ? "Checking verification..."
79
+ : "Your email hasn't been verified yet. Please check your inbox and click the verification link."}
80
+ </AlertDescription>
81
+ </Alert>
82
+ )}
83
+
84
+ <div className="space-y-4">
85
+ <ActionButton
86
+ onClick={() => void checkVerificationMutation.refetch()}
87
+ isPending={checkVerificationMutation.isFetching}
88
+ className="w-full"
89
+ >
90
+ <div className="flex items-center gap-2">
91
+ <CheckIcon className="h-4 w-4" /> Continue
92
+ </div>
93
+ </ActionButton>
94
+
95
+ <div className="relative">
96
+ <div className="absolute inset-0 flex items-center">
97
+ <span className="w-full border-t" />
98
+ </div>
99
+ <div className="relative flex justify-center text-sm">
100
+ <span className="bg-card px-2 text-muted-foreground">
101
+ Didn't receive the email?
102
+ </span>
103
+ </div>
104
+ </div>
105
+
106
+ <Button
107
+ variant="outline"
108
+ onClick={() => void resendMutation.mutate()}
109
+ disabled={resendMutation.isPending}
110
+ className="w-full gap-2"
111
+ >
112
+ {resendMutation.isPending ? (
113
+ <RefreshCw className="h-4 w-4 animate-spin" />
114
+ ) : (
115
+ <RefreshCw className="h-4 w-4" />
116
+ )}
117
+ Resend verification email
118
+ </Button>
119
+ </div>
120
+
121
+ <p className="text-center text-xs text-muted-foreground">
122
+ Make sure to check your spam folder if you don't see the email.
123
+ </p>
124
+ </CardContent>
125
+ </AuthCard>
126
+ );
127
+ };
128
+
129
+ const AuthCard = createVariantComponent(Card, "max-w-md w-full mt-10 mx-auto");