@sudobility/building_blocks 0.0.28 → 0.0.30
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.
|
@@ -3,4 +3,4 @@ export type { AppSitemapPageProps, SitemapPageText, SitemapSection, SitemapLink,
|
|
|
3
3
|
export { AppTextPage } from './app-text-page';
|
|
4
4
|
export type { AppTextPageProps, TextPageContent, TextSection, TextSectionWithContent, TextSectionWithList, TextSectionWithSubsections, TextPageContact, TextPageContactInfo, GdprNotice, } from './app-text-page';
|
|
5
5
|
export { LoginPage } from './login-page';
|
|
6
|
-
export type { LoginPageProps, LoginPageText } from './login-page';
|
|
6
|
+
export type { LoginPageProps, LoginPageText, AuthErrorInfo, } from './login-page';
|
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
2
|
import { type Auth } from 'firebase/auth';
|
|
3
|
+
/**
|
|
4
|
+
* Auth error info passed to onAuthError callback
|
|
5
|
+
*/
|
|
6
|
+
export interface AuthErrorInfo {
|
|
7
|
+
/** Firebase error code (e.g., 'auth/popup-closed-by-user') */
|
|
8
|
+
code: string;
|
|
9
|
+
/** Error message */
|
|
10
|
+
message: string;
|
|
11
|
+
/** Whether this is a user-initiated action (like closing popup) vs actual error */
|
|
12
|
+
isUserAction: boolean;
|
|
13
|
+
}
|
|
3
14
|
/**
|
|
4
15
|
* Props for the LoginPage component
|
|
5
16
|
*/
|
|
@@ -12,6 +23,8 @@ export interface LoginPageProps {
|
|
|
12
23
|
auth: Auth;
|
|
13
24
|
/** Callback fired on successful authentication */
|
|
14
25
|
onSuccess: () => void;
|
|
26
|
+
/** Callback fired on auth errors - if provided, errors won't be shown inline */
|
|
27
|
+
onAuthError?: (error: AuthErrorInfo) => void;
|
|
15
28
|
/** Whether to show Google sign-in option (default: true) */
|
|
16
29
|
showGoogleSignIn?: boolean;
|
|
17
30
|
/** Whether to show sign-up option (default: true) */
|
|
@@ -38,26 +51,4 @@ export interface LoginPageText {
|
|
|
38
51
|
alreadyHaveAccount: string;
|
|
39
52
|
dontHaveAccount: string;
|
|
40
53
|
}
|
|
41
|
-
|
|
42
|
-
* A reusable login page component with email/password and Google sign-in support.
|
|
43
|
-
*
|
|
44
|
-
* @example
|
|
45
|
-
* ```tsx
|
|
46
|
-
* import { LoginPage } from '@sudobility/building_blocks';
|
|
47
|
-
* import { getFirebaseAuth } from '@sudobility/auth_lib';
|
|
48
|
-
*
|
|
49
|
-
* function MyLoginPage() {
|
|
50
|
-
* const navigate = useNavigate();
|
|
51
|
-
* const auth = getFirebaseAuth();
|
|
52
|
-
*
|
|
53
|
-
* return (
|
|
54
|
-
* <LoginPage
|
|
55
|
-
* appName="My App"
|
|
56
|
-
* auth={auth}
|
|
57
|
-
* onSuccess={() => navigate('/')}
|
|
58
|
-
* />
|
|
59
|
-
* );
|
|
60
|
-
* }
|
|
61
|
-
* ```
|
|
62
|
-
*/
|
|
63
|
-
export declare function LoginPage({ appName, logo, auth, onSuccess, showGoogleSignIn, showSignUp, className, primaryColorClass, }: LoginPageProps): import("react/jsx-runtime").JSX.Element;
|
|
54
|
+
export declare function LoginPage({ appName, logo, auth, onSuccess, onAuthError, showGoogleSignIn, showSignUp, className, primaryColorClass, }: LoginPageProps): import("react/jsx-runtime").JSX.Element;
|
package/dist/index.js
CHANGED
|
@@ -5193,11 +5193,17 @@ const defaultText = {
|
|
|
5193
5193
|
alreadyHaveAccount: "Already have an account?",
|
|
5194
5194
|
dontHaveAccount: "Don't have an account?"
|
|
5195
5195
|
};
|
|
5196
|
+
const USER_ACTION_ERROR_CODES = [
|
|
5197
|
+
"auth/popup-closed-by-user",
|
|
5198
|
+
"auth/cancelled-popup-request",
|
|
5199
|
+
"auth/user-cancelled"
|
|
5200
|
+
];
|
|
5196
5201
|
function LoginPage({
|
|
5197
5202
|
appName,
|
|
5198
5203
|
logo,
|
|
5199
5204
|
auth,
|
|
5200
5205
|
onSuccess,
|
|
5206
|
+
onAuthError,
|
|
5201
5207
|
showGoogleSignIn = true,
|
|
5202
5208
|
showSignUp = true,
|
|
5203
5209
|
className = "",
|
|
@@ -5208,6 +5214,33 @@ function LoginPage({
|
|
|
5208
5214
|
const [password, setPassword] = useState("");
|
|
5209
5215
|
const [error, setError] = useState(null);
|
|
5210
5216
|
const [isLoading, setIsLoading] = useState(false);
|
|
5217
|
+
const [isGoogleSignInPending, setIsGoogleSignInPending] = useState(false);
|
|
5218
|
+
const googleSignInStartTime = useRef(null);
|
|
5219
|
+
useEffect(() => {
|
|
5220
|
+
const handleFocus = () => {
|
|
5221
|
+
if (isGoogleSignInPending && googleSignInStartTime.current) {
|
|
5222
|
+
const elapsed = Date.now() - googleSignInStartTime.current;
|
|
5223
|
+
if (elapsed > 2e3) {
|
|
5224
|
+
setIsLoading(false);
|
|
5225
|
+
setIsGoogleSignInPending(false);
|
|
5226
|
+
googleSignInStartTime.current = null;
|
|
5227
|
+
}
|
|
5228
|
+
}
|
|
5229
|
+
};
|
|
5230
|
+
window.addEventListener("focus", handleFocus);
|
|
5231
|
+
return () => window.removeEventListener("focus", handleFocus);
|
|
5232
|
+
}, [isGoogleSignInPending]);
|
|
5233
|
+
const handleAuthError = (err) => {
|
|
5234
|
+
const firebaseError = err;
|
|
5235
|
+
const code = firebaseError.code || "unknown";
|
|
5236
|
+
const message = firebaseError.message || "Authentication failed";
|
|
5237
|
+
const isUserAction = USER_ACTION_ERROR_CODES.includes(code);
|
|
5238
|
+
if (onAuthError) {
|
|
5239
|
+
onAuthError({ code, message, isUserAction });
|
|
5240
|
+
} else if (!isUserAction) {
|
|
5241
|
+
setError(message);
|
|
5242
|
+
}
|
|
5243
|
+
};
|
|
5211
5244
|
const handleSubmit = async (e) => {
|
|
5212
5245
|
e.preventDefault();
|
|
5213
5246
|
setError(null);
|
|
@@ -5221,7 +5254,7 @@ function LoginPage({
|
|
|
5221
5254
|
}
|
|
5222
5255
|
onSuccess();
|
|
5223
5256
|
} catch (err) {
|
|
5224
|
-
|
|
5257
|
+
handleAuthError(err);
|
|
5225
5258
|
} finally {
|
|
5226
5259
|
setIsLoading(false);
|
|
5227
5260
|
}
|
|
@@ -5229,15 +5262,19 @@ function LoginPage({
|
|
|
5229
5262
|
const handleGoogleSignIn = async () => {
|
|
5230
5263
|
setError(null);
|
|
5231
5264
|
setIsLoading(true);
|
|
5265
|
+
setIsGoogleSignInPending(true);
|
|
5266
|
+
googleSignInStartTime.current = Date.now();
|
|
5232
5267
|
try {
|
|
5233
5268
|
if (!auth) throw new Error("Firebase not configured");
|
|
5234
5269
|
const provider = new GoogleAuthProvider();
|
|
5235
5270
|
await signInWithPopup(auth, provider);
|
|
5236
5271
|
onSuccess();
|
|
5237
5272
|
} catch (err) {
|
|
5238
|
-
|
|
5273
|
+
handleAuthError(err);
|
|
5239
5274
|
} finally {
|
|
5240
5275
|
setIsLoading(false);
|
|
5276
|
+
setIsGoogleSignInPending(false);
|
|
5277
|
+
googleSignInStartTime.current = null;
|
|
5241
5278
|
}
|
|
5242
5279
|
};
|
|
5243
5280
|
const text = defaultText;
|