Skip to content

Commit 1794e60

Browse files
committed
chore(*): merged latest
2 parents 6f92fcb + dd318bf commit 1794e60

26 files changed

+495
-264
lines changed

examples/react/src/App.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { useUI } from "@invertase/firebaseui-react";
17+
import { MultiFactorAuthAssertionScreen, useUI } from "@invertase/firebaseui-react";
1818
import { multiFactor, sendEmailVerification, signOut } from "firebase/auth";
1919
import { Link, useNavigate } from "react-router";
2020
import { auth } from "./firebase/firebase";

examples/react/src/components/header.tsx

Lines changed: 0 additions & 55 deletions
This file was deleted.

examples/react/src/firebase/config.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@
1515
*/
1616

1717
export const firebaseConfig = {
18-
apiKey: "AIzaSyA7xdkFMs7iUC6XWFYjjSxf_XbVV4F1mX4",
19-
authDomain: "fir-ui-2025.firebaseapp.com",
20-
projectId: "fir-ui-2025",
21-
storageBucket: "fir-ui-2025.firebasestorage.app",
22-
messagingSenderId: "616577669988",
23-
appId: "1:616577669988:web:7e67401f952fa9288df871",
18+
apiKey: "AIzaSyCvMftIUCD9lUQ3BzIrimfSfBbCUQYZf-I",
19+
authDomain: "fir-ui-rework.firebaseapp.com",
20+
projectId: "fir-ui-rework",
21+
storageBucket: "fir-ui-rework.firebasestorage.app",
22+
messagingSenderId: "200312857118",
23+
appId: "1:200312857118:web:94e3f69b0e0a4a863f040f",
2424
};

examples/react/src/main.tsx

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@
1717
import { BrowserRouter, Routes, Route, Outlet, NavLink } from "react-router";
1818

1919
import ReactDOM from "react-dom/client";
20-
import { FirebaseUIProvider } from "@invertase/firebaseui-react";
20+
import { FirebaseUIProvider, useUI } from "@invertase/firebaseui-react";
2121
import { ui, auth } from "./firebase/firebase";
2222
import App from "./App";
2323
import { hiddenRoutes, routes } from "./routes";
24+
import { enUs } from "@invertase/firebaseui-translations";
25+
import { pirate } from "./pirate";
2426

2527
const root = document.getElementById("root")!;
2628

@@ -38,6 +40,7 @@ auth.authStateReady().then(() => {
3840
}}
3941
>
4042
<ThemeToggle />
43+
<PirateToggle />
4144
<Routes>
4245
<Route path="/" element={<App />} />
4346
<Route element={<ScreenRoute />}>
@@ -70,7 +73,7 @@ function ScreenRoute() {
7073
function ThemeToggle() {
7174
return (
7275
<button
73-
className="fixed z-10 top-8 right-8 border border-gray-300 dark:border-gray-700 rounded-md p-2 group/toggle extend-touch-target"
76+
className="fixed z-10 size-10 top-8 right-8 border border-gray-300 dark:border-gray-700 rounded-md p-2 group/toggle extend-touch-target"
7477
onClick={() => {
7578
document.documentElement.classList.toggle("dark", !document.documentElement.classList.contains("dark"));
7679
localStorage.theme = document.documentElement.classList.contains("dark") ? "dark" : "light";
@@ -100,3 +103,23 @@ function ThemeToggle() {
100103
</button>
101104
);
102105
}
106+
107+
function PirateToggle() {
108+
const ui = useUI();
109+
const isPirate = ui.locale.locale === "pirate";
110+
111+
return (
112+
<button
113+
className="fixed z-10 size-10 top-8 right-20 border border-gray-300 dark:border-gray-700 rounded-md p-2 group/toggle extend-touch-target"
114+
onClick={() => {
115+
if (isPirate) {
116+
ui.setLocale(enUs);
117+
} else {
118+
ui.setLocale(pirate);
119+
}
120+
}}
121+
>
122+
{isPirate ? "🇺🇸" : "🏴‍☠️"}
123+
</button>
124+
);
125+
}

examples/react/src/pirate.ts

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { registerLocale } from "@invertase/firebaseui-translations";
2+
3+
export const pirate = registerLocale("pirate", {
4+
errors: {
5+
userNotFound: "Arrr! No account found with this email address, matey",
6+
wrongPassword: "Arrr! Incorrect password, ye scallywag",
7+
invalidEmail: "Avast! Enter a valid email address, ye bilge rat",
8+
userDisabled: "This account has been marooned, arrr!",
9+
networkRequestFailed: "Can't connect to the server, ye land lubber! Check yer internet connection",
10+
tooManyRequests: "Too many failed attempts, ye scurvy dog! Try again later",
11+
missingVerificationCode: "Enter the verification code, ye scallywag",
12+
emailAlreadyInUse: "An account already exists with this email, arrr!",
13+
invalidCredential: "The credentials ye provided be invalid, matey",
14+
weakPassword: "Ye password ain't long enough! It should be at least 8 characters",
15+
unverifiedEmail: "Verify yer email address to continue, ye scallywag",
16+
operationNotAllowed: "This operation ain't allowed, arrr! Contact support, matey",
17+
invalidPhoneNumber: "The phone number be invalid, ye bilge rat",
18+
missingPhoneNumber: "Provide a phone number, ye scallywag",
19+
quotaExceeded: "SMS quota exceeded, arrr! Try again later, matey",
20+
codeExpired: "The verification code has expired, ye scurvy dog",
21+
captchaCheckFailed: "reCAPTCHA verification failed, arrr! Try again, matey",
22+
missingVerificationId: "Complete the reCAPTCHA verification first, ye scallywag",
23+
missingEmail: "Provide an email address, ye bilge rat",
24+
invalidActionCode: "The password reset link be invalid or has expired, arrr!",
25+
credentialAlreadyInUse: "An account already exists with this email, arrr! Sign in with that account, matey",
26+
requiresRecentLogin: "This operation requires a recent login, ye scallywag! Sign in again",
27+
providerAlreadyLinked: "This phone number be already linked to another account, arrr!",
28+
invalidVerificationCode: "Invalid verification code, ye scurvy dog! Try again",
29+
unknownError: "An unexpected error occurred, arrr!",
30+
popupClosed: "The sign-in popup was closed, ye scallywag! Try again",
31+
accountExistsWithDifferentCredential:
32+
"An account already exists with this email, arrr! Sign in with the original provider, matey",
33+
displayNameRequired: "Provide a display name, ye bilge rat",
34+
secondFactorAlreadyInUse: "This phone number be already enrolled with this account, arrr!",
35+
},
36+
messages: {
37+
passwordResetEmailSent: "Password reset email sent successfully, arrr!",
38+
signInLinkSent: "Sign-in link sent successfully, matey!",
39+
verificationCodeFirst: "Request a verification code first, ye scallywag",
40+
checkEmailForReset: "Check yer email for password reset instructions, ye bilge rat",
41+
dividerOr: "or",
42+
termsAndPrivacy: "By continuing, ye agree to our {tos} and {privacy}, arrr!",
43+
mfaSmsAssertionPrompt:
44+
"A verification code will be sent to {phoneNumber} to complete the authentication process, matey.",
45+
},
46+
labels: {
47+
emailAddress: "Email Address, ye bilge rat",
48+
password: "Password, ye scallywag",
49+
displayName: "Display Name, ye bilge rat",
50+
forgotPassword: "Forgot Password, ye scallywag?",
51+
signUp: "Sign Up, Matey",
52+
signIn: "Sign In, Matey",
53+
resetPassword: "Reset Password, ye scallywag",
54+
createAccount: "Create Account, ye bilge rat",
55+
backToSignIn: "Back to Sign In, ye scallywag",
56+
signInWithPhone: "Sign in with Phone, ye scallywag",
57+
phoneNumber: "Phone Number, ye bilge rat",
58+
verificationCode: "Verification Code, ye scallywag",
59+
sendCode: "Send Code, ye scallywag",
60+
verifyCode: "Verify Code, ye scallywag",
61+
signInWithGoogle: "Sign in with ye Google Account",
62+
signInWithFacebook: "Sign in with ye Facebook Account",
63+
signInWithApple: "Sign in with ye Apple Account",
64+
signInWithMicrosoft: "Sign in with ye Microsoft Account",
65+
signInWithGitHub: "Sign in with ye GitHub Account",
66+
signInWithTwitter: "Sign in with ye X Account",
67+
signInWithEmailLink: "Sign in with Email Link",
68+
sendSignInLink: "Send Sign-in Link",
69+
termsOfService: "Terms of Service",
70+
privacyPolicy: "Privacy Policy",
71+
resendCode: "Resend ye Code",
72+
sending: "Firing...",
73+
multiFactorEnrollment: "Multi-factor Enrrrrrrollment!",
74+
multiFactorAssertion: "Multi-factor Authentication, arrr!",
75+
mfaTotpVerification: "TOTP Verification, arrr!",
76+
mfaSmsVerification: "SMS Verification, arrr!",
77+
generateQrCode: "Generate ye QR Code",
78+
},
79+
prompts: {
80+
noAccount: "Don't have an account, ye scallywag?",
81+
haveAccount: "Already have an account, matey?",
82+
enterEmailToReset: "Enter yer email address to reset yer password, ye bilge rat",
83+
signInToAccount: "Sign in to yer account, matey",
84+
smsVerificationPrompt: "Enter the verification code sent to yer phone number, ye scallywag",
85+
enterDetailsToCreate: "Enter yer details to create a new account, ye bilge rat",
86+
enterPhoneNumber: "Enter yer phone number, matey",
87+
enterVerificationCode: "Enter the verification code, ye scallywag",
88+
enterEmailForLink: "Enter yer email to receive a sign-in link, ye bilge rat",
89+
mfaEnrollmentPrompt: "Select a new multi-factor enrollment method, arrr!",
90+
mfaAssertionPrompt: "Complete the multi-factor authentication process, ye scallywag",
91+
mfaAssertionFactorPrompt: "Choose a multi-factor authentication method, matey",
92+
mfaTotpQrCodePrompt: "Scan this QR code with yer authenticator app, ye bilge rat",
93+
mfaTotpEnrollmentVerificationPrompt: "Add the code generated by yer authenticator app, arrr!",
94+
},
95+
});

packages/core/src/translations.test.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,26 @@ describe("getTranslation", () => {
2424
const translation = getTranslation(mockUI, "errors", "userNotFound");
2525

2626
expect(translation).toBe("test + userNotFound");
27-
expect(_getTranslation).toHaveBeenCalledWith(testLocale, "errors", "userNotFound");
27+
expect(_getTranslation).toHaveBeenCalledWith(testLocale, "errors", "userNotFound", undefined);
28+
});
29+
30+
it("should pass replacements to the underlying getTranslation function", () => {
31+
const testLocale = registerLocale("test", {
32+
messages: {
33+
termsAndPrivacy: "By continuing, you agree to our {tos} and {privacy}.",
34+
},
35+
});
36+
37+
vi.mocked(_getTranslation).mockReturnValue("By continuing, you agree to our Terms of Service and Privacy Policy.");
38+
39+
const mockUI = createMockUI({ locale: testLocale });
40+
const replacements = {
41+
tos: "Terms of Service",
42+
privacy: "Privacy Policy",
43+
};
44+
const translation = getTranslation(mockUI, "messages", "termsAndPrivacy", replacements);
45+
46+
expect(translation).toBe("By continuing, you agree to our Terms of Service and Privacy Policy.");
47+
expect(_getTranslation).toHaveBeenCalledWith(testLocale, "messages", "termsAndPrivacy", replacements);
2848
});
2949
});

packages/core/src/translations.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ import {
2121
} from "@invertase/firebaseui-translations";
2222
import { type FirebaseUI } from "./config";
2323

24-
export function getTranslation<T extends TranslationCategory>(ui: FirebaseUI, category: T, key: TranslationKey<T>) {
25-
return _getTranslation(ui.locale, category, key);
24+
export function getTranslation<T extends TranslationCategory>(
25+
ui: FirebaseUI,
26+
category: T,
27+
key: TranslationKey<T>,
28+
replacements?: Record<string, string>
29+
) {
30+
return _getTranslation(ui.locale, category, key, replacements);
2631
}

packages/react/src/auth/forms/mfa/sms-multi-factor-assertion-form.test.tsx

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,10 @@ describe("<SmsMultiFactorAssertionForm />", () => {
173173
locale: registerLocale("test", {
174174
labels: {
175175
sendCode: "sendCode",
176-
phoneNumber: "phoneNumber",
176+
},
177+
messages: {
178+
mfaSmsAssertionPrompt:
179+
"A verification code will be sent to {phoneNumber} to complete the authentication process.",
177180
},
178181
}),
179182
});
@@ -195,8 +198,9 @@ describe("<SmsMultiFactorAssertionForm />", () => {
195198
const form = container.querySelectorAll("form.fui-form");
196199
expect(form.length).toBe(1);
197200

198-
expect(screen.getByRole("textbox", { name: /phoneNumber/i })).toBeInTheDocument();
199-
expect(screen.getByRole("textbox", { name: /phoneNumber/i })).toHaveValue("+1234567890");
201+
expect(
202+
screen.getByText("A verification code will be sent to +1234567890 to complete the authentication process.")
203+
).toBeInTheDocument();
200204

201205
const sendCodeButton = screen.getByRole("button", { name: "sendCode" });
202206
expect(sendCodeButton).toBeInTheDocument();
@@ -208,8 +212,9 @@ describe("<SmsMultiFactorAssertionForm />", () => {
208212
it("should display phone number from hint", () => {
209213
const mockUI = createMockUI({
210214
locale: registerLocale("test", {
211-
labels: {
212-
phoneNumber: "phoneNumber",
215+
messages: {
216+
mfaSmsAssertionPrompt:
217+
"A verification code will be sent to {phoneNumber} to complete the authentication process.",
213218
},
214219
}),
215220
});
@@ -228,15 +233,17 @@ describe("<SmsMultiFactorAssertionForm />", () => {
228233
})
229234
);
230235

231-
const phoneInput = screen.getByRole("textbox", { name: /phoneNumber/i });
232-
expect(phoneInput).toHaveValue("+1234567890");
236+
expect(
237+
screen.getByText("A verification code will be sent to +1234567890 to complete the authentication process.")
238+
).toBeInTheDocument();
233239
});
234240

235241
it("should handle missing phone number in hint", () => {
236242
const mockUI = createMockUI({
237243
locale: registerLocale("test", {
238-
labels: {
239-
phoneNumber: "phoneNumber",
244+
messages: {
245+
mfaSmsAssertionPrompt:
246+
"A verification code will be sent to {phoneNumber} to complete the authentication process.",
240247
},
241248
}),
242249
});
@@ -254,15 +261,18 @@ describe("<SmsMultiFactorAssertionForm />", () => {
254261
})
255262
);
256263

257-
const phoneInput = screen.getByRole("textbox", { name: /phoneNumber/i });
258-
expect(phoneInput).toHaveValue("");
264+
// When phone number is missing, the placeholder remains because empty string is falsy in the replacement logic
265+
expect(
266+
screen.getByText("A verification code will be sent to {phoneNumber} to complete the authentication process.")
267+
).toBeInTheDocument();
259268
});
260269

261270
it("should accept onSuccess callback prop", () => {
262271
const mockUI = createMockUI({
263272
locale: registerLocale("test", {
264-
labels: {
265-
phoneNumber: "phoneNumber",
273+
messages: {
274+
mfaSmsAssertionPrompt:
275+
"A verification code will be sent to {phoneNumber} to complete the authentication process.",
266276
},
267277
}),
268278
});
@@ -290,10 +300,13 @@ describe("<SmsMultiFactorAssertionForm />", () => {
290300
locale: registerLocale("test", {
291301
labels: {
292302
sendCode: "sendCode",
293-
phoneNumber: "phoneNumber",
294303
verificationCode: "verificationCode",
295304
verifyCode: "verifyCode",
296305
},
306+
messages: {
307+
mfaSmsAssertionPrompt:
308+
"A verification code will be sent to {phoneNumber} to complete the authentication process.",
309+
},
297310
}),
298311
});
299312

@@ -304,9 +317,7 @@ describe("<SmsMultiFactorAssertionForm />", () => {
304317
enrollmentTime: "2023-01-01T00:00:00Z",
305318
};
306319

307-
// First step returns a verificationId
308320
vi.mocked(verifyPhoneNumber).mockResolvedValue("vid-123");
309-
// Second step returns a credential from MFA assertion
310321
const mockCredential = { user: { uid: "sms-cred-user" } } as any;
311322
vi.mocked(signInWithMultiFactorAssertion).mockResolvedValue(mockCredential);
312323

0 commit comments

Comments
 (0)