zudoku 0.66.2 → 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.
- package/dist/config/validators/validate.d.ts +32 -3
- package/dist/config/validators/validate.js +1 -1
- package/dist/config/validators/validate.js.map +1 -1
- package/dist/flat-config.d.ts +1 -1
- package/dist/lib/auth/issuer.js +1 -1
- package/dist/lib/auth/issuer.js.map +1 -1
- package/dist/lib/authentication/authentication.d.ts +3 -2
- package/dist/lib/authentication/hook.d.ts +2 -0
- package/dist/lib/authentication/hook.js +10 -0
- package/dist/lib/authentication/hook.js.map +1 -1
- package/dist/lib/authentication/providers/firebase.js +67 -9
- package/dist/lib/authentication/providers/firebase.js.map +1 -1
- package/dist/lib/authentication/ui/EmailVerificationUi.d.ts +4 -0
- package/dist/lib/authentication/ui/EmailVerificationUi.js +34 -0
- package/dist/lib/authentication/ui/EmailVerificationUi.js.map +1 -0
- package/dist/lib/authentication/ui/ZudokuAuthUi.d.ts +7 -2
- package/dist/lib/authentication/ui/ZudokuAuthUi.js +43 -11
- package/dist/lib/authentication/ui/ZudokuAuthUi.js.map +1 -1
- package/dist/lib/authentication/utils/relativeRedirectUrl.d.ts +1 -0
- package/dist/lib/authentication/utils/relativeRedirectUrl.js +8 -0
- package/dist/lib/authentication/utils/relativeRedirectUrl.js.map +1 -0
- package/dist/lib/components/index.d.ts +2 -0
- package/dist/lib/errors/ErrorMessage.d.ts +3 -0
- package/dist/lib/errors/ErrorMessage.js +16 -0
- package/dist/lib/errors/ErrorMessage.js.map +1 -0
- package/dist/lib/hooks/index.d.ts +2 -0
- package/dist/lib/plugins/api-keys/SettingsApiKeys.js +9 -172
- package/dist/lib/plugins/api-keys/SettingsApiKeys.js.map +1 -1
- package/dist/lib/plugins/api-keys/index.d.ts +4 -1
- package/dist/lib/plugins/api-keys/index.js +19 -14
- package/dist/lib/plugins/api-keys/index.js.map +1 -1
- package/dist/lib/plugins/api-keys/settings/ApiKeyItem.d.ts +12 -0
- package/dist/lib/plugins/api-keys/settings/ApiKeyItem.js +133 -0
- package/dist/lib/plugins/api-keys/settings/ApiKeyItem.js.map +1 -0
- package/dist/lib/plugins/api-keys/settings/ApiKeyList.d.ts +4 -0
- package/dist/lib/plugins/api-keys/settings/ApiKeyList.js +30 -0
- package/dist/lib/plugins/api-keys/settings/ApiKeyList.js.map +1 -0
- package/dist/lib/plugins/api-keys/settings/RevealApiKey.d.ts +6 -0
- package/dist/lib/plugins/api-keys/settings/RevealApiKey.js +39 -0
- package/dist/lib/plugins/api-keys/settings/RevealApiKey.js.map +1 -0
- package/dist/vite/config.js +7 -0
- package/dist/vite/config.js.map +1 -1
- package/dist/vite/plugin-api-keys.js +4 -1
- package/dist/vite/plugin-api-keys.js.map +1 -1
- package/dist/vite/zuplo.d.ts +13 -0
- package/dist/vite/zuplo.js +15 -0
- package/dist/vite/zuplo.js.map +1 -0
- package/lib/{ClaudeLogo-CGRfGTk2.js → ClaudeLogo-BZslN9XF.js} +3 -3
- package/lib/{ClaudeLogo-CGRfGTk2.js.map → ClaudeLogo-BZslN9XF.js.map} +1 -1
- package/lib/{Drawer-Ci7XwhqT.js → Drawer-BRMcpfuF.js} +6 -6
- package/lib/{Drawer-Ci7XwhqT.js.map → Drawer-BRMcpfuF.js.map} +1 -1
- package/lib/Frame-DxlznfVd.js +205 -0
- package/lib/Frame-DxlznfVd.js.map +1 -0
- package/lib/{useMutation-C6RqWmTS.js → Input-D-kqEQ5M.js} +41 -23
- package/lib/Input-D-kqEQ5M.js.map +1 -0
- package/lib/{MdxPage-Bjf72BP3.js → MdxPage-B4zZq5aR.js} +8 -8
- package/lib/{MdxPage-Bjf72BP3.js.map → MdxPage-B4zZq5aR.js.map} +1 -1
- package/lib/{Mermaid-D_VSX7_Q.js → Mermaid-BjSczjLW.js} +3 -3
- package/lib/{Mermaid-D_VSX7_Q.js.map → Mermaid-BjSczjLW.js.map} +1 -1
- package/lib/{OAuthErrorPage-1Ekji0PK.js → OAuthErrorPage-DRY2hlga.js} +20 -21
- package/lib/{OAuthErrorPage-1Ekji0PK.js.map → OAuthErrorPage-DRY2hlga.js.map} +1 -1
- package/lib/{OasProvider-BZxmTyMM.js → OasProvider-lMwTD76Y.js} +4 -4
- package/lib/{OasProvider-BZxmTyMM.js.map → OasProvider-lMwTD76Y.js.map} +1 -1
- package/lib/{OperationList-B7nPIFB8.js → OperationList-Bm76b4vl.js} +23 -24
- package/lib/{OperationList-B7nPIFB8.js.map → OperationList-Bm76b4vl.js.map} +1 -1
- package/lib/{RouteGuard-9wjejsKm.js → RouteGuard-DGc32XJV.js} +4 -4
- package/lib/{RouteGuard-9wjejsKm.js.map → RouteGuard-DGc32XJV.js.map} +1 -1
- package/lib/{SchemaList-16_obkku.js → SchemaList-DX4FPogg.js} +7 -7
- package/lib/{SchemaList-16_obkku.js.map → SchemaList-DX4FPogg.js.map} +1 -1
- package/lib/SchemaView-CjXvSRxy.js +434 -0
- package/lib/SchemaView-CjXvSRxy.js.map +1 -0
- package/lib/{Select-CkxXP5I7.js → Secret-BxGpIhDP.js} +121 -121
- package/lib/Secret-BxGpIhDP.js.map +1 -0
- package/lib/{SignUp-D54_QWFy.js → SignUp-CntxjFGS.js} +4 -4
- package/lib/{SignUp-D54_QWFy.js.map → SignUp-CntxjFGS.js.map} +1 -1
- package/lib/{SyntaxHighlight-j_HRSPCU.js → SyntaxHighlight-Dgd0AaaX.js} +2 -2
- package/lib/{SyntaxHighlight-j_HRSPCU.js.map → SyntaxHighlight-Dgd0AaaX.js.map} +1 -1
- package/lib/{Toc-z05x698-.js → Toc-L1vGGHA1.js} +2 -2
- package/lib/{Toc-z05x698-.js.map → Toc-L1vGGHA1.js.map} +1 -1
- package/lib/{ZudokuContext-BXldanA8.js → ZudokuContext-DNHMZfcP.js} +33 -33
- package/lib/{ZudokuContext-BXldanA8.js.map → ZudokuContext-DNHMZfcP.js.map} +1 -1
- package/lib/{chunk-PVWAREVJ-dLIqswPy.js → chunk-PVWAREVJ-ClM0m2aJ.js} +19 -19
- package/lib/{chunk-PVWAREVJ-dLIqswPy.js.map → chunk-PVWAREVJ-ClM0m2aJ.js.map} +1 -1
- package/lib/{circular-D5sYCIWL.js → circular-BIN_WQ0c.js} +2 -2
- package/lib/{circular-D5sYCIWL.js.map → circular-BIN_WQ0c.js.map} +1 -1
- package/lib/{createServer-BlwU7lIr.js → createServer-BEl12QFw.js} +4 -4
- package/lib/{createServer-BlwU7lIr.js.map → createServer-BEl12QFw.js.map} +1 -1
- package/lib/createVariantComponent-CQVt-H3r.js +18 -0
- package/lib/createVariantComponent-CQVt-H3r.js.map +1 -0
- package/lib/{errors-BtC4Kn2j.js → errors-CtBbD47A.js} +2 -2
- package/lib/{errors-BtC4Kn2j.js.map → errors-CtBbD47A.js.map} +1 -1
- package/lib/{firebase-Ibm_tv3G.js → firebase-CT38ugg4.js} +1588 -1342
- package/lib/firebase-CT38ugg4.js.map +1 -0
- package/lib/hook-CHw_R_xu.js +52 -0
- package/lib/hook-CHw_R_xu.js.map +1 -0
- package/lib/{ErrorAlert-BUlG32M9.js → index-CG2v-T7-.js} +5373 -4335
- package/lib/index-CG2v-T7-.js.map +1 -0
- package/lib/{index-eKVhlB94.js → index-CISwSpdT.js} +2 -2
- package/lib/{index-eKVhlB94.js.map → index-CISwSpdT.js.map} +1 -1
- package/lib/{index-Css56y3F.js → index-DXXZDuSJ.js} +4 -4
- package/lib/{index-Css56y3F.js.map → index-DXXZDuSJ.js.map} +1 -1
- package/lib/{index-CeVTNcfF.js → index-jI2Fjpy-.js} +98 -99
- package/lib/{index-CeVTNcfF.js.map → index-jI2Fjpy-.js.map} +1 -1
- package/lib/{index.esm-BoKBnRoT.js → index.esm-Bu35TNgg.js} +16 -14
- package/lib/index.esm-Bu35TNgg.js.map +1 -0
- package/lib/{mutation-BoVlx8yA.js → mutation-DMHWqmFp.js} +2 -2
- package/lib/{mutation-BoVlx8yA.js.map → mutation-DMHWqmFp.js.map} +1 -1
- package/lib/ui/Drawer.js +2 -2
- package/lib/ui/SyntaxHighlight.js +2 -2
- package/lib/zudoku.__internal.js +507 -479
- package/lib/zudoku.__internal.js.map +1 -1
- package/lib/zudoku.auth-auth0.js +1 -1
- package/lib/zudoku.auth-azureb2c.js +4 -4
- package/lib/zudoku.auth-clerk.js +2 -2
- package/lib/zudoku.auth-firebase.js +6 -5
- package/lib/zudoku.auth-firebase.js.map +1 -1
- package/lib/zudoku.auth-openid.js +4 -4
- package/lib/zudoku.auth-supabase.js +5 -5
- package/lib/zudoku.components.js +20 -21
- package/lib/zudoku.components.js.map +1 -1
- package/lib/zudoku.hooks.js +3 -3
- package/lib/zudoku.mermaid.js +3 -3
- package/lib/zudoku.plugin-api-catalog.js +8 -9
- package/lib/zudoku.plugin-api-catalog.js.map +1 -1
- package/lib/zudoku.plugin-api-keys.js +580 -543
- package/lib/zudoku.plugin-api-keys.js.map +1 -1
- package/lib/zudoku.plugin-custom-pages.js +1 -1
- package/lib/zudoku.plugin-markdown.js +1 -1
- package/lib/zudoku.plugin-openapi.js +3 -3
- package/lib/zudoku.plugin-redirect.js +1 -1
- package/lib/zudoku.plugin-search-pagefind.js +3 -3
- package/lib/zudoku.router.js +2 -2
- package/package.json +4 -2
- package/src/lib/auth/issuer.ts +1 -1
- package/src/lib/authentication/authentication.ts +8 -2
- package/src/lib/authentication/hook.ts +16 -0
- package/src/lib/authentication/providers/firebase.tsx +98 -6
- package/src/lib/authentication/ui/EmailVerificationUi.tsx +129 -0
- package/src/lib/authentication/ui/ZudokuAuthUi.tsx +170 -38
- package/src/lib/authentication/utils/relativeRedirectUrl.ts +12 -0
- package/src/lib/errors/ErrorMessage.tsx +38 -0
- package/src/lib/plugins/api-keys/SettingsApiKeys.tsx +36 -476
- package/src/lib/plugins/api-keys/index.tsx +33 -18
- package/src/lib/plugins/api-keys/settings/ApiKeyItem.tsx +342 -0
- package/src/lib/plugins/api-keys/settings/ApiKeyList.tsx +64 -0
- package/src/lib/plugins/api-keys/settings/RevealApiKey.tsx +124 -0
- package/lib/ErrorAlert-BUlG32M9.js.map +0 -1
- package/lib/RouterError-DfTZblpv.js +0 -42
- package/lib/RouterError-DfTZblpv.js.map +0 -1
- package/lib/SchemaView-eyvR4bRt.js +0 -597
- package/lib/SchemaView-eyvR4bRt.js.map +0 -1
- package/lib/Select-CkxXP5I7.js.map +0 -1
- package/lib/createVariantComponent-B9_dVBvu.js +0 -35
- package/lib/createVariantComponent-B9_dVBvu.js.map +0 -1
- package/lib/firebase-Ibm_tv3G.js.map +0 -1
- package/lib/hook-BNxidGQq.js +0 -40
- package/lib/hook-BNxidGQq.js.map +0 -1
- package/lib/index-DSOi7zVM.js +0 -1059
- package/lib/index-DSOi7zVM.js.map +0 -1
- package/lib/index.esm-BoKBnRoT.js.map +0 -1
- package/lib/useMutation-C6RqWmTS.js.map +0 -1
|
@@ -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 {
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
257
|
+
await this.setUserLoggedIn(user);
|
|
166
258
|
} else {
|
|
167
259
|
useAuthState.setState({ isPending: false });
|
|
168
260
|
}
|
|
169
261
|
};
|
|
170
262
|
|
|
171
|
-
private async
|
|
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");
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
} from "../../ui/Form.js";
|
|
24
24
|
import { cn } from "../../util/cn.js";
|
|
25
25
|
import createVariantComponent from "../../util/createVariantComponent.js";
|
|
26
|
+
import { getRelativeRedirectUrl } from "../utils/relativeRedirectUrl.js";
|
|
26
27
|
import AppleIcon from "./icons/Apple.js";
|
|
27
28
|
import FacebookIcon from "./icons/Facebook.js";
|
|
28
29
|
import GithubIcon from "./icons/Github.js";
|
|
@@ -137,8 +138,10 @@ export const ZudokuSignInUi = ({
|
|
|
137
138
|
providers,
|
|
138
139
|
onOAuthSignIn,
|
|
139
140
|
onUsernamePasswordSignIn,
|
|
141
|
+
enableUsernamePassword,
|
|
140
142
|
}: {
|
|
141
143
|
providers: string[];
|
|
144
|
+
enableUsernamePassword: boolean;
|
|
142
145
|
onOAuthSignIn: (providerId: string) => Promise<void>;
|
|
143
146
|
onUsernamePasswordSignIn: (email: string, password: string) => Promise<void>;
|
|
144
147
|
}) => {
|
|
@@ -146,6 +149,8 @@ export const ZudokuSignInUi = ({
|
|
|
146
149
|
const [searchParams] = useSearchParams();
|
|
147
150
|
const redirectTo = searchParams.get("redirectTo");
|
|
148
151
|
|
|
152
|
+
const relativeRedirectTo = getRelativeRedirectUrl(redirectTo);
|
|
153
|
+
|
|
149
154
|
const invalidProviders = providers.filter(
|
|
150
155
|
(provider) => !isValidAuthProviderId(provider),
|
|
151
156
|
);
|
|
@@ -165,7 +170,7 @@ export const ZudokuSignInUi = ({
|
|
|
165
170
|
await onUsernamePasswordSignIn(email, password);
|
|
166
171
|
},
|
|
167
172
|
onSuccess: () => {
|
|
168
|
-
void navigate(
|
|
173
|
+
void navigate(relativeRedirectTo);
|
|
169
174
|
},
|
|
170
175
|
});
|
|
171
176
|
const signInByProviderMutation = useMutation({
|
|
@@ -173,7 +178,7 @@ export const ZudokuSignInUi = ({
|
|
|
173
178
|
await onOAuthSignIn(providerId);
|
|
174
179
|
},
|
|
175
180
|
onSuccess: () => {
|
|
176
|
-
void navigate(
|
|
181
|
+
void navigate(relativeRedirectTo);
|
|
177
182
|
},
|
|
178
183
|
});
|
|
179
184
|
|
|
@@ -202,24 +207,38 @@ export const ZudokuSignInUi = ({
|
|
|
202
207
|
<AlertDescription>{error?.message}</AlertDescription>
|
|
203
208
|
</Alert>
|
|
204
209
|
)}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
210
|
+
{enableUsernamePassword && (
|
|
211
|
+
<>
|
|
212
|
+
<EmailPasswordForm
|
|
213
|
+
form={form}
|
|
214
|
+
onSubmit={(data) =>
|
|
215
|
+
void signInUsernameMutation.mutate({
|
|
216
|
+
email: data.email,
|
|
217
|
+
password: data.password,
|
|
218
|
+
})
|
|
219
|
+
}
|
|
220
|
+
submitLabel="Sign in"
|
|
221
|
+
isPending={pending}
|
|
222
|
+
/>
|
|
223
|
+
<Link
|
|
224
|
+
to="/reset-password"
|
|
225
|
+
className="text-sm text-muted-foreground text-right -mt-2"
|
|
226
|
+
>
|
|
227
|
+
Forgot password?
|
|
228
|
+
</Link>
|
|
229
|
+
</>
|
|
230
|
+
)}
|
|
231
|
+
{enableUsernamePassword && providers.length > 0 && (
|
|
232
|
+
<ProviderSeparator providers={providers} />
|
|
233
|
+
)}
|
|
234
|
+
{providers.length > 0 && (
|
|
235
|
+
<ProviderButtons
|
|
236
|
+
providers={providers}
|
|
237
|
+
onClick={(providerId) =>
|
|
238
|
+
signInByProviderMutation.mutate({ providerId })
|
|
239
|
+
}
|
|
240
|
+
/>
|
|
241
|
+
)}
|
|
223
242
|
<Link to="/signup" className="text-sm text-muted-foreground">
|
|
224
243
|
Don't have an account? Sign up.
|
|
225
244
|
</Link>
|
|
@@ -230,13 +249,21 @@ export const ZudokuSignInUi = ({
|
|
|
230
249
|
|
|
231
250
|
export const ZudokuSignUpUi = ({
|
|
232
251
|
providers,
|
|
252
|
+
enableUsernamePassword,
|
|
233
253
|
onOAuthSignUp,
|
|
234
254
|
onUsernamePasswordSignUp,
|
|
235
255
|
}: {
|
|
236
256
|
providers: string[];
|
|
257
|
+
enableUsernamePassword: boolean;
|
|
237
258
|
onOAuthSignUp: (providerId: string) => Promise<void>;
|
|
238
259
|
onUsernamePasswordSignUp: (email: string, password: string) => Promise<void>;
|
|
239
260
|
}) => {
|
|
261
|
+
const navigate = useNavigate();
|
|
262
|
+
const [searchParams] = useSearchParams();
|
|
263
|
+
const redirectTo = searchParams.get("redirectTo");
|
|
264
|
+
|
|
265
|
+
const relativeRedirectTo = redirectTo?.replace(window.location.origin, "");
|
|
266
|
+
|
|
240
267
|
if (!isAuthProviderIdArray(providers)) {
|
|
241
268
|
throw new Error("Invalid auth provider IDs");
|
|
242
269
|
}
|
|
@@ -245,12 +272,18 @@ export const ZudokuSignUpUi = ({
|
|
|
245
272
|
mutationFn: async ({ email, password }: FormFields) => {
|
|
246
273
|
await onUsernamePasswordSignUp(email, password);
|
|
247
274
|
},
|
|
275
|
+
onSuccess: () => {
|
|
276
|
+
void navigate(relativeRedirectTo ?? "/");
|
|
277
|
+
},
|
|
248
278
|
});
|
|
249
279
|
|
|
250
280
|
const signUpByProviderMutation = useMutation({
|
|
251
281
|
mutationFn: async ({ providerId }: { providerId: string }) => {
|
|
252
282
|
await onOAuthSignUp(providerId);
|
|
253
283
|
},
|
|
284
|
+
onSuccess: () => {
|
|
285
|
+
void navigate(relativeRedirectTo ?? "/");
|
|
286
|
+
},
|
|
254
287
|
});
|
|
255
288
|
|
|
256
289
|
const form = useForm<FormFields>({
|
|
@@ -279,24 +312,30 @@ export const ZudokuSignUpUi = ({
|
|
|
279
312
|
</Alert>
|
|
280
313
|
)}
|
|
281
314
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
315
|
+
{enableUsernamePassword && (
|
|
316
|
+
<EmailPasswordForm
|
|
317
|
+
form={form}
|
|
318
|
+
onSubmit={(data) =>
|
|
319
|
+
void signUpUsernameMutation.mutate({
|
|
320
|
+
email: data.email,
|
|
321
|
+
password: data.password,
|
|
322
|
+
})
|
|
323
|
+
}
|
|
324
|
+
submitLabel="Sign up"
|
|
325
|
+
isPending={pending}
|
|
326
|
+
/>
|
|
327
|
+
)}
|
|
328
|
+
{enableUsernamePassword && providers.length > 0 && (
|
|
329
|
+
<ProviderSeparator providers={providers} />
|
|
330
|
+
)}
|
|
331
|
+
{providers.length > 0 && (
|
|
332
|
+
<ProviderButtons
|
|
333
|
+
providers={providers}
|
|
334
|
+
onClick={(providerId) =>
|
|
335
|
+
signUpByProviderMutation.mutate({ providerId })
|
|
336
|
+
}
|
|
337
|
+
/>
|
|
338
|
+
)}
|
|
300
339
|
<Link to="/signin" className="text-sm text-muted-foreground">
|
|
301
340
|
Already have an account? Sign in.
|
|
302
341
|
</Link>
|
|
@@ -343,3 +382,96 @@ const ProviderSeparator = ({ providers }: { providers: AuthProviderId[] }) => {
|
|
|
343
382
|
)
|
|
344
383
|
);
|
|
345
384
|
};
|
|
385
|
+
|
|
386
|
+
export const ZudokuPasswordResetUi = ({
|
|
387
|
+
onPasswordReset,
|
|
388
|
+
}: {
|
|
389
|
+
onPasswordReset: (email: string) => Promise<void>;
|
|
390
|
+
}) => {
|
|
391
|
+
const [isSubmitted, setIsSubmitted] = React.useState(false);
|
|
392
|
+
|
|
393
|
+
const passwordResetMutation = useMutation({
|
|
394
|
+
mutationFn: async ({ email }: { email: string }) => {
|
|
395
|
+
await onPasswordReset(email);
|
|
396
|
+
},
|
|
397
|
+
onSuccess: () => {
|
|
398
|
+
setIsSubmitted(true);
|
|
399
|
+
},
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
const form = useForm<{ email: string }>({
|
|
403
|
+
defaultValues: {
|
|
404
|
+
email: "",
|
|
405
|
+
},
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
const error = passwordResetMutation.error;
|
|
409
|
+
|
|
410
|
+
return (
|
|
411
|
+
<AuthCard>
|
|
412
|
+
<CardHeader>
|
|
413
|
+
<CardTitle>Reset password</CardTitle>
|
|
414
|
+
<CardDescription>
|
|
415
|
+
{isSubmitted
|
|
416
|
+
? "Check your email for a password reset link."
|
|
417
|
+
: "Enter your email address and we'll send you a link to reset your password."}
|
|
418
|
+
</CardDescription>
|
|
419
|
+
</CardHeader>
|
|
420
|
+
<CardContent className="flex flex-col gap-4">
|
|
421
|
+
{error && (
|
|
422
|
+
<Alert variant="destructive">
|
|
423
|
+
<AlertTitle>Error</AlertTitle>
|
|
424
|
+
<AlertDescription>{error?.message}</AlertDescription>
|
|
425
|
+
</Alert>
|
|
426
|
+
)}
|
|
427
|
+
{isSubmitted ? (
|
|
428
|
+
<div className="flex flex-col gap-4">
|
|
429
|
+
<Alert>
|
|
430
|
+
<AlertTitle>Email sent</AlertTitle>
|
|
431
|
+
<AlertDescription>
|
|
432
|
+
If an account exists with that email address, you will receive a
|
|
433
|
+
password reset link shortly.
|
|
434
|
+
</AlertDescription>
|
|
435
|
+
</Alert>
|
|
436
|
+
<Link to="/signin">
|
|
437
|
+
<Button variant="outline" className="w-full">
|
|
438
|
+
Back to sign in
|
|
439
|
+
</Button>
|
|
440
|
+
</Link>
|
|
441
|
+
</div>
|
|
442
|
+
) : (
|
|
443
|
+
<>
|
|
444
|
+
<Form {...form}>
|
|
445
|
+
<form
|
|
446
|
+
onSubmit={form.handleSubmit((data) =>
|
|
447
|
+
passwordResetMutation.mutate({ email: data.email }),
|
|
448
|
+
)}
|
|
449
|
+
className="flex flex-col gap-2"
|
|
450
|
+
>
|
|
451
|
+
<FormItem>
|
|
452
|
+
<FormLabel>Email</FormLabel>
|
|
453
|
+
<FormControl>
|
|
454
|
+
<Input
|
|
455
|
+
placeholder="you@example.com"
|
|
456
|
+
{...form.register("email")}
|
|
457
|
+
/>
|
|
458
|
+
</FormControl>
|
|
459
|
+
<FormMessage />
|
|
460
|
+
</FormItem>
|
|
461
|
+
<ActionButton
|
|
462
|
+
type="submit"
|
|
463
|
+
isPending={passwordResetMutation.isPending}
|
|
464
|
+
>
|
|
465
|
+
Reset password
|
|
466
|
+
</ActionButton>
|
|
467
|
+
</form>
|
|
468
|
+
</Form>
|
|
469
|
+
<Link to="/signin" className="text-sm text-muted-foreground">
|
|
470
|
+
Sign in
|
|
471
|
+
</Link>
|
|
472
|
+
</>
|
|
473
|
+
)}
|
|
474
|
+
</CardContent>
|
|
475
|
+
</AuthCard>
|
|
476
|
+
);
|
|
477
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { joinUrl } from "../../util/joinUrl.js";
|
|
2
|
+
|
|
3
|
+
export const getRelativeRedirectUrl = (redirectTo?: string | null) => {
|
|
4
|
+
if (!redirectTo) {
|
|
5
|
+
return "/";
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
return redirectTo.replace(
|
|
9
|
+
joinUrl(window.location.origin, import.meta.env.BASE_URL),
|
|
10
|
+
"",
|
|
11
|
+
);
|
|
12
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Alert, AlertDescription, AlertTitle } from "zudoku/ui/Alert.js";
|
|
2
|
+
import { SyntaxHighlight } from "zudoku/ui/SyntaxHighlight.js";
|
|
3
|
+
import { DeveloperHint } from "../components/DeveloperHint.js";
|
|
4
|
+
import { ZudokuError } from "../util/invariant.js";
|
|
5
|
+
|
|
6
|
+
export function ErrorMessage({ error }: { error: unknown }) {
|
|
7
|
+
const message =
|
|
8
|
+
error instanceof Error ? error.message : "Something went wrong";
|
|
9
|
+
|
|
10
|
+
const showDeveloperHints = process.env.NODE_ENV === "development";
|
|
11
|
+
|
|
12
|
+
const hint = error instanceof ZudokuError ? error.developerHint : undefined;
|
|
13
|
+
const title =
|
|
14
|
+
error instanceof ZudokuError ? error.title : "Something went wrong";
|
|
15
|
+
const stack = error instanceof Error ? error.stack : undefined;
|
|
16
|
+
const cause = error instanceof Error ? error.cause : undefined;
|
|
17
|
+
|
|
18
|
+
const stringError = cause instanceof Error ? String(cause.stack) : stack;
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<>
|
|
22
|
+
<Alert variant="destructive">
|
|
23
|
+
<AlertTitle>{title}</AlertTitle>
|
|
24
|
+
<AlertDescription>{message}</AlertDescription>
|
|
25
|
+
</Alert>
|
|
26
|
+
{showDeveloperHints && hint && (
|
|
27
|
+
<DeveloperHint className="mb-4">{hint}</DeveloperHint>
|
|
28
|
+
)}
|
|
29
|
+
{showDeveloperHints && stringError && (
|
|
30
|
+
<SyntaxHighlight
|
|
31
|
+
className="max-h-[400px] [&>pre]:p-4"
|
|
32
|
+
language="js"
|
|
33
|
+
code={stringError}
|
|
34
|
+
/>
|
|
35
|
+
)}
|
|
36
|
+
</>
|
|
37
|
+
);
|
|
38
|
+
}
|