Skip to content

Commit 18fe144

Browse files
committed
chore: Update example app
1 parent c54483b commit 18fe144

35 files changed

+681
-228
lines changed

examples/shadcn/index.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
<link rel="stylesheet" href="src/index.css" />
99
</head>
1010
<body>
11+
<script>
12+
document.documentElement.classList.toggle("dark", localStorage.theme === "dark" || (!("theme" in localStorage) && window.matchMedia("(prefers-color-scheme: dark)").matches));
13+
</script>
1114
<div id="root"></div>
1215
<script type="module" src="/src/main.tsx"></script>
1316
</body>

examples/shadcn/src/App.tsx

Lines changed: 111 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,21 @@ import { useUser } from "./firebase/hooks";
2020
import { auth } from "./firebase/firebase";
2121
import { multiFactor, sendEmailVerification, signOut } from "firebase/auth";
2222

23+
import {
24+
Item,
25+
ItemActions,
26+
ItemContent,
27+
ItemDescription,
28+
ItemFooter,
29+
ItemGroup,
30+
ItemMedia,
31+
ItemSeparator,
32+
ItemTitle,
33+
} from "@/components/ui/item";
34+
import { Button } from "./components/ui/button";
35+
import { ArrowRightIcon, LockIcon, UserIcon } from "lucide-react";
36+
import React from "react";
37+
2338
function App() {
2439
const user = useUser();
2540

@@ -36,83 +51,117 @@ function UnauthenticatedApp() {
3651
<div className="text-center space-y-4">
3752
<img src="/firebase-logo-inverted.png" alt="Firebase UI" className="hidden dark:block h-36 mx-auto" />
3853
<img src="/firebase-logo.png" alt="Firebase UI" className="block dark:hidden h-36 mx-auto" />
39-
<p className="text-sm text-gray-700 dark:text-gray-300">
54+
<p className="text-sm text-muted-foreground">
4055
Welcome to Firebase UI, choose an example screen below to get started!
4156
</p>
4257
</div>
43-
<div className="border border-neutral-800 rounded divide-y divide-neutral-800 overflow-hidden">
58+
<ItemGroup className="border rounded-md">
4459
{routes.map((route) => (
45-
<Link
46-
key={route.path}
47-
to={route.path}
48-
className="flex items-center justify-between hover:bg-neutral-100 dark:bg-neutral-900 dark:hover:bg-neutral-800 p-4"
49-
>
50-
<div className="space-y-1">
51-
<h2 className="font-medium text-sm">{route.name}</h2>
52-
<p className="text-xs text-gray-400 dark:text-gray-300">{route.description}</p>
53-
</div>
54-
<div>
55-
<span className="text-xl">&rarr;</span>
56-
</div>
57-
</Link>
60+
<React.Fragment key={route.path}>
61+
<Item>
62+
<ItemContent>
63+
<ItemTitle>{route.name}</ItemTitle>
64+
<ItemDescription className="text-xs">{route.description}</ItemDescription>
65+
</ItemContent>
66+
<ItemActions>
67+
<Link to={route.path}>
68+
<Button size="icon" variant="outline">
69+
<ArrowRightIcon />
70+
</Button>
71+
</Link>
72+
</ItemActions>
73+
</Item>
74+
<ItemSeparator />
75+
</React.Fragment>
5876
))}
59-
</div>
77+
</ItemGroup>
6078
</div>
6179
);
6280
}
6381

6482
function AuthenticatedApp() {
6583
const user = useUser()!;
84+
console.log(user);
6685
const mfa = multiFactor(user);
6786
const navigate = useNavigate();
6887

6988
return (
70-
<div className="max-w-sm mx-auto pt-36 space-y-6 pb-36">
71-
<div className="border border-neutral-800 rounded p-4 space-y-4">
72-
<h1 className="text-md font-medium">Welcome, {user.displayName || user.email || user.phoneNumber}</h1>
73-
{user.emailVerified ? (
74-
<div className="text-green-500">Email verified</div>
75-
) : (
76-
<button
77-
className="bg-red-500 text-white px-3 py-1.5 rounded text-sm"
78-
onClick={async () => {
79-
try {
80-
await sendEmailVerification(user);
81-
alert("Email verification sent, please check your email");
82-
} catch (error) {
83-
console.error(error);
84-
alert("Error sending email verification, check console");
85-
}
86-
}}
87-
>
88-
Verify Email &rarr;
89-
</button>
90-
)}
91-
<hr className="opacity-20" />
92-
<h2 className="text-sm font-medium">Multi-factor Authentication</h2>
93-
{mfa.enrolledFactors.map((factor) => {
94-
return (
95-
<div key={factor.factorId}>
96-
{factor.factorId} - {factor.displayName}
97-
</div>
98-
);
99-
})}
100-
<button
101-
className="bg-blue-500 text-white px-3 py-1.5 rounded text-sm"
102-
onClick={() => {
103-
navigate("/screens/mfa-enrollment-screen");
104-
}}
105-
>
106-
Add MFA Factor &rarr;
107-
</button>
108-
<hr className="opacity-20" />
109-
<button
110-
className="bg-blue-500 text-white px-3 py-1.5 rounded text-sm"
111-
onClick={async () => await signOut(auth)}
112-
>
113-
Sign Out &rarr;
114-
</button>
115-
</div>
89+
<div className="max-w-lg mx-auto pt-36 space-y-6 pb-36">
90+
<ItemGroup className="border rounded-md">
91+
<Item>
92+
<ItemMedia variant="icon">
93+
<UserIcon />
94+
</ItemMedia>
95+
<ItemContent>
96+
<ItemTitle>Welcome, {user.displayName || user.email || user.phoneNumber}</ItemTitle>
97+
<ItemDescription>New login detected from unknown device.</ItemDescription>
98+
</ItemContent>
99+
<ItemActions>
100+
<Button size="sm" variant="outline" onClick={async () => await signOut(auth)}>
101+
Sign Out
102+
</Button>
103+
</ItemActions>
104+
{user.email ? (
105+
<ItemFooter className="pl-12">
106+
{user.emailVerified ? (
107+
<Item>
108+
<ItemDescription>Your email is verified.</ItemDescription>
109+
</Item>
110+
) : (
111+
<>
112+
<ItemDescription>Your email is not verified.</ItemDescription>
113+
<ItemActions>
114+
<Button
115+
variant="secondary"
116+
size="sm"
117+
onClick={async () => {
118+
await sendEmailVerification(user);
119+
alert("Email verification sent, please check your email");
120+
}}
121+
>
122+
Verify Email &rarr;
123+
</Button>
124+
</ItemActions>
125+
</>
126+
)}
127+
</ItemFooter>
128+
) : null}
129+
</Item>
130+
<ItemSeparator />
131+
<Item>
132+
<ItemMedia variant="icon">
133+
<LockIcon />
134+
</ItemMedia>
135+
<ItemContent>
136+
<ItemTitle>Multi-factor Authentication</ItemTitle>
137+
<ItemDescription>
138+
Any multi-factor authentication factors you have enrolled will be listed here.
139+
</ItemDescription>
140+
</ItemContent>
141+
<ItemActions>
142+
<Button
143+
size="sm"
144+
variant="outline"
145+
onClick={() => {
146+
navigate("/screens/mfa-enrollment-screen");
147+
}}
148+
>
149+
Add Factor
150+
</Button>
151+
</ItemActions>
152+
{mfa.enrolledFactors.length > 0 && (
153+
<ItemFooter className="pl-12">
154+
{mfa.enrolledFactors.map((factor) => {
155+
return (
156+
<div key={factor.factorId} className="text-sm text-muted-foreground">
157+
{factor.factorId} - {factor.displayName}
158+
</div>
159+
);
160+
})}
161+
</ItemFooter>
162+
)}
163+
</Item>
164+
</ItemGroup>
116165
</div>
117166
);
118167
}

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ import { useState } from "react";
1616
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
1717
import { Input } from "@/components/ui/input";
1818
import { Button } from "@/components/ui/button";
19-
import { Policies } from "./policies";
19+
import { Policies } from "@/components/policies";
20+
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
2021

2122
export type { EmailLinkAuthFormProps };
2223

@@ -49,15 +50,15 @@ export function EmailLinkAuthForm(props: EmailLinkAuthFormProps) {
4950

5051
if (emailSent) {
5152
return (
52-
<div className="text-center space-y-4">
53-
<div className="text-green-600 dark:text-green-400">{getTranslation(ui, "messages", "signInLinkSent")}</div>
54-
</div>
53+
<Alert>
54+
<AlertDescription>{getTranslation(ui, "messages", "signInLinkSent")}</AlertDescription>
55+
</Alert>
5556
);
5657
}
5758

5859
return (
5960
<Form {...form}>
60-
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-2">
61+
<form onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col gap-y-4">
6162
<FormField
6263
control={form.control}
6364
name="email"

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

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { useUI, type EmailLinkAuthScreenProps } from "@invertase/firebaseui-reac
66
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
77
import { Separator } from "@/components/ui/separator";
88
import { EmailLinkAuthForm } from "@/components/email-link-auth-form";
9+
import { MultiFactorAuthAssertionForm } from "@/components/multi-factor-auth-assertion-form";
10+
import { RedirectError } from "@/components/redirect-error";
911

1012
export type { EmailLinkAuthScreenProps };
1113

@@ -14,22 +16,32 @@ export function EmailLinkAuthScreen({ children, ...props }: EmailLinkAuthScreenP
1416

1517
const titleText = getTranslation(ui, "labels", "signIn");
1618
const subtitleText = getTranslation(ui, "prompts", "signInToAccount");
19+
const mfaResolver = ui.multiFactorResolver;
1720

1821
return (
19-
<div className="max-w-md mx-auto">
22+
<div className="max-w-sm mx-auto">
2023
<Card>
2124
<CardHeader>
2225
<CardTitle>{titleText}</CardTitle>
2326
<CardDescription>{subtitleText}</CardDescription>
2427
</CardHeader>
2528
<CardContent>
26-
<EmailLinkAuthForm {...props} />
27-
{children ? (
29+
{mfaResolver ? (
30+
<MultiFactorAuthAssertionForm onSuccess={(credential) => props.onSignIn?.(credential)} />
31+
) : (
2832
<>
29-
<Separator>{getTranslation(ui, "messages", "dividerOr")}</Separator>
30-
<div className="space-y-2">{children}</div>
33+
<EmailLinkAuthForm {...props} />
34+
{children ? (
35+
<>
36+
<Separator className="my-4" />
37+
<div className="space-y-2">
38+
{children}
39+
<RedirectError />
40+
</div>
41+
</>
42+
) : null}
3143
</>
32-
) : null}
44+
)}
3345
</CardContent>
3446
</Card>
3547
</div>

examples/shadcn/src/components/forgot-password-auth-form.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export function ForgotPasswordAuthForm(props: ForgotPasswordAuthFormProps) {
5353

5454
return (
5555
<Form {...form}>
56-
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-2">
56+
<form onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col gap-y-4">
5757
<FormField
5858
control={form.control}
5959
name="email"
@@ -73,8 +73,8 @@ export function ForgotPasswordAuthForm(props: ForgotPasswordAuthFormProps) {
7373
</Button>
7474
{form.formState.errors.root && <FormMessage>{form.formState.errors.root.message}</FormMessage>}
7575
{props.onBackToSignInClick ? (
76-
<Button type="button" variant="secondary" onClick={props.onBackToSignInClick}>
77-
{getTranslation(ui, "labels", "backToSignIn")}
76+
<Button type="button" variant="link" size="sm" onClick={props.onBackToSignInClick}>
77+
<span className="text-xs">&larr; {getTranslation(ui, "labels", "backToSignIn")}</span>
7878
</Button>
7979
) : null}
8080
</form>

examples/shadcn/src/components/forgot-password-auth-screen.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export function ForgotPasswordAuthScreen(props: ForgotPasswordAuthScreenProps) {
1515
const subtitleText = getTranslation(ui, "prompts", "enterEmailToReset");
1616

1717
return (
18-
<div className="max-w-md mx-auto">
18+
<div className="max-w-sm mx-auto">
1919
<Card>
2020
<CardHeader>
2121
<CardTitle>{titleText}</CardTitle>

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

Lines changed: 16 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
import {
44
PhoneMultiFactorGenerator,
55
TotpMultiFactorGenerator,
6-
type MultiFactorInfo,
76
type UserCredential,
7+
type MultiFactorInfo,
88
} from "firebase/auth";
99
import { type ComponentProps, useState } from "react";
1010
import { getTranslation } from "@invertase/firebaseui-core";
@@ -18,7 +18,7 @@ export type MultiFactorAuthAssertionFormProps = {
1818
onSuccess?: (credential: UserCredential) => void;
1919
};
2020

21-
export function MultiFactorAuthAssertionForm(props: MultiFactorAuthAssertionFormProps) {
21+
export function MultiFactorAuthAssertionForm({ onSuccess }: MultiFactorAuthAssertionFormProps) {
2222
const ui = useUI();
2323
const resolver = ui.multiFactorResolver;
2424

@@ -33,31 +33,17 @@ export function MultiFactorAuthAssertionForm(props: MultiFactorAuthAssertionForm
3333

3434
if (hint) {
3535
if (hint.factorId === PhoneMultiFactorGenerator.FACTOR_ID) {
36-
return (
37-
<SmsMultiFactorAssertionForm
38-
hint={hint}
39-
onSuccess={(credential) => {
40-
props.onSuccess?.(credential);
41-
}}
42-
/>
43-
);
36+
return <SmsMultiFactorAssertionForm hint={hint} onSuccess={(credential) => onSuccess?.(credential)} />;
4437
}
4538

4639
if (hint.factorId === TotpMultiFactorGenerator.FACTOR_ID) {
47-
return (
48-
<TotpMultiFactorAssertionForm
49-
hint={hint}
50-
onSuccess={(credential) => {
51-
props.onSuccess?.(credential);
52-
}}
53-
/>
54-
);
40+
return <TotpMultiFactorAssertionForm hint={hint} onSuccess={(credential) => onSuccess?.(credential)} />;
5541
}
5642
}
5743

5844
return (
59-
<div className="space-y-2">
60-
<p className="text-sm text-muted-foreground">Select a multi-factor authentication method</p>
45+
<div className="flex flex-col gap-2">
46+
<p className="text-sm text-muted-foreground">TODO:Select a multi-factor authentication method</p>
6147
{resolver.hints.map((hint) => {
6248
if (hint.factorId === TotpMultiFactorGenerator.FACTOR_ID) {
6349
return <TotpButton key={hint.factorId} onClick={() => setHint(hint)} />;
@@ -76,11 +62,19 @@ export function MultiFactorAuthAssertionForm(props: MultiFactorAuthAssertionForm
7662
function TotpButton(props: ComponentProps<typeof Button>) {
7763
const ui = useUI();
7864
const labelText = getTranslation(ui, "labels", "mfaTotpVerification");
79-
return <Button {...props}>{labelText}</Button>;
65+
return (
66+
<Button {...props} variant="outline">
67+
{labelText}
68+
</Button>
69+
);
8070
}
8171

8272
function SmsButton(props: ComponentProps<typeof Button>) {
8373
const ui = useUI();
8474
const labelText = getTranslation(ui, "labels", "mfaSmsVerification");
85-
return <Button {...props}>{labelText}</Button>;
75+
return (
76+
<Button {...props} variant="outline">
77+
{labelText}
78+
</Button>
79+
);
8680
}

0 commit comments

Comments
 (0)