Skip to content

Commit 79d43cf

Browse files
committed
Merge branch '@invertase/v7-development' of https://github.com/firebase/firebaseui-web into @invertase/update-next-ssg-example
2 parents 99f8b53 + dea622d commit 79d43cf

16 files changed

+152
-22
lines changed

packages/core/src/index.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,29 @@
1515
* limitations under the License.
1616
*/
1717

18-
import { registerFramework } from "./register-framework";
1918
import pkgJson from "../package.json";
19+
import { registerFramework } from "./register-framework";
2020

2121
export * from "./auth";
2222
export * from "./behaviors";
2323
export * from "./config";
24+
export * from "./country-data";
2425
export * from "./errors";
26+
export * from "./register-framework";
2527
export * from "./schemas";
26-
export * from "./country-data";
2728
export * from "./translations";
28-
export * from "./register-framework";
2929

30-
if (import.meta.env.PROD) {
31-
registerFramework("core", pkgJson.version);
30+
// Detect production mode across different build systems (Vite, webpack/Next.js, etc.)
31+
const isDevelopment = typeof process !== "undefined" && process.env.NODE_ENV === "production";
32+
33+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
34+
const isViteProduction = (import.meta as any)?.env?.PROD === true;
35+
36+
// Check if in production mode
37+
const isProduction = isDevelopment || isViteProduction;
38+
39+
if (isProduction) {
40+
// Extract framework name from package name (e.g., "@invertase/firebaseui-react" -> "react")
41+
const frameworkName = pkgJson.name.replace("@invertase/firebaseui-", "");
42+
registerFramework(frameworkName, pkgJson.version);
3243
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,9 @@ describe("<MultiFactorEnrollmentVerifyPhoneNumberForm />", () => {
217217
verificationCode: "verificationCode",
218218
verifyCode: "verifyCode",
219219
},
220+
prompts: {
221+
mfaSmsEnrollmentVerificationPrompt: "mfaSmsEnrollmentVerificationPrompt",
222+
},
220223
}),
221224
});
222225

@@ -238,6 +241,10 @@ describe("<MultiFactorEnrollmentVerifyPhoneNumberForm />", () => {
238241

239242
expect(screen.getByRole("textbox", { name: /verificationCode/i })).toBeInTheDocument();
240243

244+
const description = container.querySelector("[data-input-description]");
245+
expect(description).toBeInTheDocument();
246+
expect(description).toHaveTextContent("mfaSmsEnrollmentVerificationPrompt");
247+
241248
const verifyCodeButton = screen.getByRole("button", { name: "verifyCode" });
242249
expect(verifyCodeButton).toBeInTheDocument();
243250
expect(verifyCodeButton).toHaveAttribute("type", "submit");

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,13 @@ export function MultiFactorEnrollmentVerifyPhoneNumberForm(props: MultiFactorEnr
193193
<form.AppForm>
194194
<fieldset>
195195
<form.AppField name="verificationCode">
196-
{(field) => <field.Input label={getTranslation(ui, "labels", "verificationCode")} type="text" />}
196+
{(field) => (
197+
<field.Input
198+
description={getTranslation(ui, "prompts", "mfaSmsEnrollmentVerificationPrompt")}
199+
label={getTranslation(ui, "labels", "verificationCode")}
200+
type="text"
201+
/>
202+
)}
197203
</form.AppField>
198204
</fieldset>
199205
<fieldset>

packages/react/src/auth/forms/mfa/totp-multi-factor-enrollment-form.test.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ describe("<MultiFactorEnrollmentVerifyTotpForm />", () => {
171171
},
172172
prompts: {
173173
mfaTotpQrCodePrompt: "Scan this QR code with your authenticator app",
174+
mfaTotpEnrollmentVerificationPrompt: "Add the code generated by your authenticator app",
174175
},
175176
}),
176177
});
@@ -189,6 +190,10 @@ describe("<MultiFactorEnrollmentVerifyTotpForm />", () => {
189190

190191
expect(screen.getByRole("textbox", { name: /verificationCode/i })).toBeInTheDocument();
191192

193+
const description = container.querySelector("[data-input-description]");
194+
expect(description).toBeInTheDocument();
195+
expect(description).toHaveTextContent("Add the code generated by your authenticator app");
196+
192197
const verifyCodeButton = screen.getByRole("button", { name: "verifyCode" });
193198
expect(verifyCodeButton).toBeInTheDocument();
194199
expect(verifyCodeButton).toHaveAttribute("type", "submit");

packages/react/src/auth/forms/mfa/totp-multi-factor-enrollment-form.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,12 +155,19 @@ export function MultiFactorEnrollmentVerifyTotpForm(props: MultiFactorEnrollment
155155
>
156156
<div className="fui-qr-code-container">
157157
<img src={qrCodeDataUrl} alt="TOTP QR Code" />
158+
<code>{props.secret.secretKey.toString()}</code>
158159
<p>{getTranslation(ui, "prompts", "mfaTotpQrCodePrompt")}</p>
159160
</div>
160161
<form.AppForm>
161162
<fieldset>
162163
<form.AppField name="verificationCode">
163-
{(field) => <field.Input label={getTranslation(ui, "labels", "verificationCode")} type="text" />}
164+
{(field) => (
165+
<field.Input
166+
label={getTranslation(ui, "labels", "verificationCode")}
167+
type="text"
168+
description={getTranslation(ui, "prompts", "mfaTotpEnrollmentVerificationPrompt")}
169+
/>
170+
)}
164171
</form.AppField>
165172
</fieldset>
166173
<fieldset>

packages/react/src/components/form.test.tsx

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,47 @@ describe("form export", () => {
120120
expect(screen.getByTestId("test-action")).toHaveTextContent("Action");
121121
});
122122

123+
it("should render the Input description prop when provided", () => {
124+
const { result } = renderHook(() => {
125+
return form.useAppForm({
126+
defaultValues: { foo: "bar" },
127+
});
128+
});
129+
130+
const hook = result.current;
131+
132+
const { container } = render(
133+
<hook.AppForm>
134+
<hook.AppField name="foo">
135+
{(field) => <field.Input label="Foo" description="This is a description" />}
136+
</hook.AppField>
137+
</hook.AppForm>
138+
);
139+
140+
const description = container.querySelector("[data-input-description]");
141+
expect(description).toBeInTheDocument();
142+
expect(description).toHaveTextContent("This is a description");
143+
});
144+
145+
it("should not render the Input description when not provided", () => {
146+
const { result } = renderHook(() => {
147+
return form.useAppForm({
148+
defaultValues: { foo: "bar" },
149+
});
150+
});
151+
152+
const hook = result.current;
153+
154+
const { container } = render(
155+
<hook.AppForm>
156+
<hook.AppField name="foo">{(field) => <field.Input label="Foo" />}</hook.AppField>
157+
</hook.AppForm>
158+
);
159+
160+
const description = container.querySelector("[data-input-description]");
161+
expect(description).not.toBeInTheDocument();
162+
});
163+
123164
it("should render the Input metadata when available", async () => {
124165
const { result } = renderHook(() => {
125166
return form.useAppForm({

packages/react/src/components/form.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,11 @@ function Input({
2424
before,
2525
label,
2626
action,
27+
description,
2728
...props
28-
}: PropsWithChildren<ComponentProps<"input"> & { label: string; before?: ReactNode; action?: ReactNode }>) {
29+
}: PropsWithChildren<
30+
ComponentProps<"input"> & { label: string; before?: ReactNode; action?: ReactNode; description?: ReactNode }
31+
>) {
2932
const field = useFieldContext<string>();
3033

3134
return (
@@ -34,6 +37,7 @@ function Input({
3437
<div>{label}</div>
3538
{action ? <div>{action}</div> : null}
3639
</div>
40+
{description ? <div data-input-description>{description}</div> : null}
3741
<div data-input-group>
3842
{before}
3943
<input

packages/react/src/index.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,17 @@ export { PolicyContext } from "./components/policies";
2424
export { FirebaseUIProvider, type FirebaseUIProviderProps } from "./context";
2525
export * from "./hooks";
2626

27-
if (import.meta.env.PROD) {
28-
registerFramework("react", pkgJson.version);
27+
// Detect production mode across different build systems (Vite, webpack/Next.js, etc.)
28+
const isNodeProduction = typeof process !== "undefined" && process.env.NODE_ENV === "production";
29+
30+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
31+
const isViteProduction = (import.meta as any)?.env?.PROD === true;
32+
33+
// Check if in production mode
34+
const isProduction = isNodeProduction || isViteProduction;
35+
36+
if (isProduction) {
37+
// Extract framework name from package name (e.g., "@invertase/firebaseui-react" -> "react")
38+
const frameworkName = pkgJson.name.replace("@invertase/firebaseui-", "");
39+
registerFramework(frameworkName, pkgJson.version);
2940
}

packages/shadcn/src/components/sms-multi-factor-enrollment-form.test.tsx

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,9 @@ describe("<SmsMultiFactorEnrollmentForm />", () => {
127127
verificationCode: "Verification Code",
128128
verifyCode: "Verify Code",
129129
},
130+
prompts: {
131+
mfaSmsEnrollmentVerificationPrompt: "mfaSmsEnrollmentVerificationPrompt",
132+
},
130133
}),
131134
});
132135

@@ -149,6 +152,10 @@ describe("<SmsMultiFactorEnrollmentForm />", () => {
149152
expect(screen.getByTestId("input-otp")).toBeInTheDocument();
150153
});
151154

155+
const description = container.querySelector('[data-slot="form-description"]');
156+
expect(description).toBeInTheDocument();
157+
expect(description).toHaveTextContent("mfaSmsEnrollmentVerificationPrompt");
158+
152159
expect(screen.getByRole("button", { name: "Verify Code" })).toBeInTheDocument();
153160
});
154161

@@ -196,6 +203,9 @@ describe("<SmsMultiFactorEnrollmentForm />", () => {
196203
verificationCode: "Verification Code",
197204
verifyCode: "Verify Code",
198205
},
206+
prompts: {
207+
mfaSmsEnrollmentVerificationPrompt: "mfaSmsEnrollmentVerificationPrompt",
208+
},
199209
}),
200210
});
201211

@@ -206,7 +216,6 @@ describe("<SmsMultiFactorEnrollmentForm />", () => {
206216
})
207217
);
208218

209-
// Fill in display name first
210219
const displayNameInput = container.querySelector("input[name='displayName']")!;
211220
fireEvent.change(displayNameInput, { target: { value: "Test User" } });
212221

@@ -218,6 +227,10 @@ describe("<SmsMultiFactorEnrollmentForm />", () => {
218227
expect(screen.getByTestId("input-otp")).toBeInTheDocument();
219228
});
220229

230+
const description = container.querySelector('[data-slot="form-description"]');
231+
expect(description).toBeInTheDocument();
232+
expect(description).toHaveTextContent("mfaSmsEnrollmentVerificationPrompt");
233+
221234
const verificationInput = screen.getByTestId("input-otp-slot-0");
222235
fireEvent.change(verificationInput, { target: { value: "123456" } });
223236
fireEvent.click(screen.getByRole("button", { name: "Verify Code" }));
@@ -239,6 +252,9 @@ describe("<SmsMultiFactorEnrollmentForm />", () => {
239252
verificationCode: "Verification Code",
240253
verifyCode: "Verify Code",
241254
},
255+
prompts: {
256+
mfaSmsEnrollmentVerificationPrompt: "mfaSmsEnrollmentVerificationPrompt",
257+
},
242258
}),
243259
});
244260

@@ -249,7 +265,6 @@ describe("<SmsMultiFactorEnrollmentForm />", () => {
249265
})
250266
);
251267

252-
// Fill in display name first
253268
const displayNameInput = container.querySelector("input[name='displayName']")!;
254269
fireEvent.change(displayNameInput, { target: { value: "Test User" } });
255270

@@ -261,6 +276,10 @@ describe("<SmsMultiFactorEnrollmentForm />", () => {
261276
expect(screen.getByTestId("input-otp")).toBeInTheDocument();
262277
});
263278

279+
const description = container.querySelector('[data-slot="form-description"]');
280+
expect(description).toBeInTheDocument();
281+
expect(description).toHaveTextContent("mfaSmsEnrollmentVerificationPrompt");
282+
264283
const verificationInput = screen.getByTestId("input-otp-slot-0");
265284
fireEvent.change(verificationInput, { target: { value: "123456" } });
266285
fireEvent.click(screen.getByRole("button", { name: "Verify Code" }));

packages/shadcn/src/components/sms-multi-factor-enrollment-form.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import {
1919
import { useForm } from "react-hook-form";
2020
import { standardSchemaResolver } from "@hookform/resolvers/standard-schema";
2121

22-
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
22+
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
2323
import { Input } from "@/components/ui/input";
2424
import { Button } from "@/components/ui/button";
2525
import { InputOTP, InputOTPGroup, InputOTPSlot } from "@/components/ui/input-otp";
@@ -136,6 +136,7 @@ export function MultiFactorEnrollmentVerifyPhoneNumberForm(props: MultiFactorEnr
136136
render={({ field }) => (
137137
<FormItem>
138138
<FormLabel>{getTranslation(ui, "labels", "verificationCode")}</FormLabel>
139+
<FormDescription>{getTranslation(ui, "prompts", "mfaSmsEnrollmentVerificationPrompt")}</FormDescription>
139140
<FormControl>
140141
<InputOTP maxLength={6} {...field}>
141142
<InputOTPGroup>

0 commit comments

Comments
 (0)