Skip to content

Commit a12a06f

Browse files
committed
chore: Sync shadc components + add pirate mode
1 parent dd318bf commit a12a06f

11 files changed

+203
-160
lines changed

examples/shadcn/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@
1212
},
1313
"dependencies": {
1414
"@hookform/resolvers": "^5.2.2",
15-
"@invertase/firebaseui-core": "*",
15+
"@invertase/firebaseui-core": "workspace:*",
1616
"@invertase/firebaseui-react": "workspace:*",
17-
"@invertase/firebaseui-styles": "*",
18-
"@invertase/firebaseui-translations": "*",
17+
"@invertase/firebaseui-styles": "workspace:*",
18+
"@invertase/firebaseui-translations": "workspace:*",
1919
"@radix-ui/react-accordion": "^1.2.12",
2020
"@radix-ui/react-alert-dialog": "^1.1.15",
2121
"@radix-ui/react-aspect-ratio": "^1.1.7",

examples/shadcn/src/components/email-link-auth-form.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
"use client";
22

3+
import { standardSchemaResolver } from "@hookform/resolvers/standard-schema";
34
import type { EmailLinkAuthFormSchema } from "@invertase/firebaseui-core";
5+
import { FirebaseUIError, getTranslation } from "@invertase/firebaseui-core";
46
import {
5-
useUI,
67
useEmailLinkAuthFormAction,
7-
useEmailLinkAuthFormSchema,
88
useEmailLinkAuthFormCompleteSignIn,
9+
useEmailLinkAuthFormSchema,
10+
useUI,
911
type EmailLinkAuthFormProps,
1012
} from "@invertase/firebaseui-react";
11-
import { useForm } from "react-hook-form";
12-
import { standardSchemaResolver } from "@hookform/resolvers/standard-schema";
13-
import { FirebaseUIError, getTranslation } from "@invertase/firebaseui-core";
1413
import { useState } from "react";
14+
import { useForm } from "react-hook-form";
1515

16+
import { Policies } from "@/components/policies";
17+
import { Alert, AlertDescription } from "@/components/ui/alert";
18+
import { Button } from "@/components/ui/button";
1619
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
1720
import { Input } from "@/components/ui/input";
18-
import { Button } from "@/components/ui/button";
19-
import { Policies } from "@/components/policies";
20-
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
2121

2222
export type { EmailLinkAuthFormProps };
2323

examples/shadcn/src/components/multi-factor-auth-assertion-form.tsx

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,30 @@
11
"use client";
22

3-
import { PhoneMultiFactorGenerator, TotpMultiFactorGenerator, type MultiFactorInfo } from "firebase/auth";
4-
import { type ComponentProps, useState } from "react";
53
import { getTranslation } from "@invertase/firebaseui-core";
64
import { useUI } from "@invertase/firebaseui-react";
7-
import { useEffect } from "react";
5+
import {
6+
PhoneMultiFactorGenerator,
7+
TotpMultiFactorGenerator,
8+
type MultiFactorInfo,
9+
type UserCredential,
10+
} from "firebase/auth";
11+
import { useState, type ComponentProps } from "react";
12+
import { useMultiFactorAssertionCleanup } from "@invertase/firebaseui-react";
813

914
import { SmsMultiFactorAssertionForm } from "@/components/sms-multi-factor-assertion-form";
1015
import { TotpMultiFactorAssertionForm } from "@/components/totp-multi-factor-assertion-form";
1116
import { Button } from "@/components/ui/button";
1217

13-
export function MultiFactorAuthAssertionForm() {
18+
export type MultiFactorAuthAssertionFormProps = {
19+
onSuccess?: (credential: UserCredential) => void;
20+
};
21+
22+
export function MultiFactorAuthAssertionForm({ onSuccess }: MultiFactorAuthAssertionFormProps) {
1423
const ui = useUI();
1524
const resolver = ui.multiFactorResolver;
1625
const mfaAssertionFactorPrompt = getTranslation(ui, "prompts", "mfaAssertionFactorPrompt");
1726

18-
useEffect(() => {
19-
return () => {
20-
ui.setMultiFactorResolver();
21-
};
22-
}, []);
27+
useMultiFactorAssertionCleanup();
2328

2429
if (!resolver) {
2530
throw new Error("MultiFactorAuthAssertionForm requires a multi-factor resolver");
@@ -32,11 +37,11 @@ export function MultiFactorAuthAssertionForm() {
3237

3338
if (hint) {
3439
if (hint.factorId === PhoneMultiFactorGenerator.FACTOR_ID) {
35-
return <SmsMultiFactorAssertionForm hint={hint} />;
40+
return <SmsMultiFactorAssertionForm hint={hint} onSuccess={onSuccess} />;
3641
}
3742

3843
if (hint.factorId === TotpMultiFactorGenerator.FACTOR_ID) {
39-
return <TotpMultiFactorAssertionForm hint={hint} />;
44+
return <TotpMultiFactorAssertionForm hint={hint} onSuccess={onSuccess} />;
4045
}
4146
}
4247

examples/shadcn/src/components/sign-up-auth-form.tsx

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -88,21 +88,6 @@ export function SignUpAuthForm(props: SignUpAuthFormProps) {
8888
</FormItem>
8989
)}
9090
/>
91-
{requireDisplayName ? (
92-
<FormField
93-
control={form.control}
94-
name="displayName"
95-
render={({ field }) => (
96-
<FormItem>
97-
<FormLabel>{getTranslation(ui, "labels", "displayName")}</FormLabel>
98-
<FormControl>
99-
<Input {...field} />
100-
</FormControl>
101-
<FormMessage />
102-
</FormItem>
103-
)}
104-
/>
105-
) : null}
10691
<Policies />
10792
<Button type="submit" disabled={ui.state !== "idle"}>
10893
{getTranslation(ui, "labels", "createAccount")}

examples/shadcn/src/components/sms-multi-factor-assertion-form.tsx

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
import { useForm } from "react-hook-form";
1515
import { standardSchemaResolver } from "@hookform/resolvers/standard-schema";
1616

17-
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
17+
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
1818
import { Button } from "@/components/ui/button";
1919
import { InputOTP, InputOTPGroup, InputOTPSlot } from "@/components/ui/input-otp";
2020

@@ -47,12 +47,14 @@ function SmsMultiFactorAssertionPhoneForm(props: SmsMultiFactorAssertionPhoneFor
4747

4848
return (
4949
<div className="space-y-4">
50-
<div>
51-
<label className="text-sm font-medium text-gray-700">{getTranslation(ui, "labels", "phoneNumber")}</label>
52-
<div className="mt-1 p-3 bg-gray-50 border rounded-md text-gray-600">
53-
{(props.hint as PhoneMultiFactorInfo).phoneNumber || "No phone number available"}
54-
</div>
55-
</div>
50+
<FormItem>
51+
<FormLabel>{getTranslation(ui, "labels", "phoneNumber")}</FormLabel>
52+
<FormDescription>
53+
{getTranslation(ui, "messages", "mfaSmsAssertionPrompt", {
54+
phoneNumber: (props.hint as PhoneMultiFactorInfo).phoneNumber || "",
55+
})}
56+
</FormDescription>
57+
</FormItem>
5658
<div className="fui-recaptcha-container" ref={recaptchaContainerRef} />
5759
<Button onClick={onSubmit} disabled={ui.state !== "idle"}>
5860
{getTranslation(ui, "labels", "sendCode")}
@@ -102,6 +104,7 @@ function SmsMultiFactorAssertionVerifyForm(props: SmsMultiFactorAssertionVerifyF
102104
render={({ field }) => (
103105
<FormItem>
104106
<FormLabel>{getTranslation(ui, "labels", "verificationCode")}</FormLabel>
107+
<FormDescription>{getTranslation(ui, "prompts", "smsVerificationPrompt")}</FormDescription>
105108
<FormControl>
106109
<InputOTP maxLength={6} {...field}>
107110
<InputOTPGroup>

examples/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", "smsVerificationPrompt")}</FormDescription>
139140
<FormControl>
140141
<InputOTP maxLength={6} {...field}>
141142
<InputOTPGroup>

examples/shadcn/src/components/totp-multi-factor-enrollment-form.tsx

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -104,13 +104,12 @@ export function MultiFactorEnrollmentVerifyTotpForm(props: MultiFactorEnrollment
104104

105105
return (
106106
<div className="space-y-4">
107-
<div className="flex justify-center">
108-
<div className="border rounded-lg p-4">
109-
<img src={qrCodeDataUrl} alt="TOTP QR Code" className="mx-auto" />
110-
<p className="text-sm text-muted-foreground text-center mt-2">
111-
{getTranslation(ui, "prompts", "mfaTotpQrCodePrompt")}
112-
</p>
113-
</div>
107+
<div className="flex flex-col gap-y-4 items-center justify-center">
108+
<img src={qrCodeDataUrl} alt="TOTP QR Code" className="mx-auto" />
109+
<code className="text-xs text-muted-foreground text-center">{props.secret.secretKey.toString()}</code>
110+
<p className="text-xs text-muted-foreground text-center">
111+
{getTranslation(ui, "prompts", "mfaTotpQrCodePrompt")}
112+
</p>
114113
</div>
115114
<Form {...form}>
116115
<form onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col gap-y-4">

examples/shadcn/src/main.tsx

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@
1717
import { BrowserRouter, Routes, Route, Outlet, Link } 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 { Button } from "@/components/ui/button";
2424
import { hiddenRoutes, routes } from "./routes";
25+
import { enUs } from "@invertase/firebaseui-translations";
26+
import { pirate } from "./pirate";
2527

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

@@ -39,6 +41,7 @@ auth.authStateReady().then(() => {
3941
}}
4042
>
4143
<ThemeToggle />
44+
<PirateToggle />
4245
<Routes>
4346
<Route path="/" element={<App />} />
4447
<Route element={<ScreenRoute />}>
@@ -102,3 +105,26 @@ function ThemeToggle() {
102105
</Button>
103106
);
104107
}
108+
109+
function PirateToggle() {
110+
const ui = useUI();
111+
const isPirate = ui.locale.locale === "pirate";
112+
113+
return (
114+
<Button
115+
variant="outline"
116+
size="icon"
117+
className="fixed z-10 top-8 right-20 group/toggle extend-touch-target size-8"
118+
onClick={() => {
119+
if (isPirate) {
120+
ui.setLocale(enUs);
121+
} else {
122+
ui.setLocale(pirate);
123+
}
124+
}}
125+
title="Toggle pirate mode"
126+
>
127+
{isPirate ? "🇺🇸" : "🏴‍☠️"}
128+
</Button>
129+
);
130+
}

examples/shadcn/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+
});

examples/shadcn/src/screens/oauth-screen.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import { GitHubSignInButton } from "@/components/github-sign-in-button";
2323
import { MicrosoftSignInButton } from "@/components/microsoft-sign-in-button";
2424
import { TwitterSignInButton } from "@/components/twitter-sign-in-button";
2525
import { OAuthScreen } from "@/components/oauth-screen";
26-
import { useState } from "react";
2726

2827
export default function OAuthScreenPage() {
2928
const [themed, setThemed] = useState(false);

0 commit comments

Comments
 (0)