Skip to content

Commit fb9676d

Browse files
authored
feat(angular): Updated Angular package and example application
2 parents 3504d1e + 824ca23 commit fb9676d

File tree

107 files changed

+2513
-1144
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

107 files changed

+2513
-1144
lines changed

examples/angular/package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,10 @@
3232
"@angular/platform-server": "^20.2.2",
3333
"@angular/router": "^20.2.2",
3434
"@angular/ssr": "^20.2.2",
35-
"@invertase/firebaseui-angular": "0.0.3",
36-
"@invertase/firebaseui-core": "0.0.3",
37-
"@invertase/firebaseui-styles": "0.0.7",
38-
"@invertase/firebaseui-translations": "0.0.4",
35+
"@invertase/firebaseui-angular": "workspace:*",
36+
"@invertase/firebaseui-core": "workspace:*",
37+
"@invertase/firebaseui-styles": "workspace:*",
38+
"@invertase/firebaseui-translations": "workspace:*",
3939
"@tailwindcss/postcss": "^4.0.6",
4040
"express": "^4.18.2",
4141
"postcss": "^8.5.2",
9.86 KB
Loading
10.7 KB
Loading

examples/angular/src/app/app.component.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@
1414
limitations under the License.
1515
-->
1616

17-
<div class="app-container">
18-
<router-outlet />
19-
</div>
17+
<app-theme-toggle />
18+
<app-pirate-toggle />
19+
<router-outlet />

examples/angular/src/app/app.component.ts

Lines changed: 113 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -14,43 +14,125 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { Component } from "@angular/core";
18-
import { RouterOutlet } from "@angular/router";
17+
import { Component, computed, inject, input } from "@angular/core";
18+
import { RouterModule, Router } from "@angular/router";
1919
import { CommonModule } from "@angular/common";
20-
import { HeaderComponent } from "./components/header";
20+
import { Auth, multiFactor, sendEmailVerification, signOut, type User } from "@angular/fire/auth";
21+
import { routes } from "./routes";
22+
import { ThemeToggleComponent } from "./components/theme-toggle/theme-toggle.component";
23+
import { PirateToggleComponent } from "./components/pirate-toggle/pirate-toggle.component";
24+
import { MultiFactorAuthAssertionScreenComponent } from "@invertase/firebaseui-angular";
25+
import { injectUI } from "@invertase/firebaseui-angular";
2126

2227
@Component({
23-
selector: "app-root",
28+
selector: "app-unauthenticated",
29+
standalone: true,
30+
imports: [CommonModule, RouterModule, MultiFactorAuthAssertionScreenComponent],
31+
template: `
32+
@if (mfaResolver()) {
33+
<fui-multi-factor-auth-assertion-screen />
34+
} @else {
35+
<div class="max-w-sm mx-auto pt-36 space-y-6 pb-36">
36+
<div class="text-center space-y-4">
37+
<img src="/firebase-logo-inverted.png" alt="Firebase UI" class="hidden dark:block h-36 mx-auto" />
38+
<img src="/firebase-logo.png" alt="Firebase UI" class="block dark:hidden h-36 mx-auto" />
39+
<p class="text-sm text-gray-700 dark:text-gray-300">
40+
Welcome to Firebase UI, choose an example screen below to get started!
41+
</p>
42+
</div>
43+
<div
44+
class="border border-neutral-200 dark:border-neutral-800 rounded divide-y divide-neutral-200 dark:divide-neutral-800 overflow-hidden"
45+
>
46+
@for (route of routes; track route.path) {
47+
<a
48+
[routerLink]="route.path"
49+
class="flex items-center justify-between hover:bg-neutral-100 dark:bg-neutral-900 dark:hover:bg-neutral-800 p-4"
50+
>
51+
<div class="space-y-1">
52+
<h2 class="font-medium text-sm">{{ route.name }}</h2>
53+
<p class="text-xs text-gray-400 dark:text-gray-300">{{ route.description }}</p>
54+
</div>
55+
<div class="text-neutral-600 dark:text-neutral-400">
56+
<span class="text-xl">&rarr;</span>
57+
</div>
58+
</a>
59+
}
60+
</div>
61+
</div>
62+
}
63+
`,
64+
})
65+
export class UnauthenticatedAppComponent {
66+
ui = injectUI();
67+
routes = routes;
68+
69+
mfaResolver = computed(() => this.ui().multiFactorResolver);
70+
}
71+
72+
@Component({
73+
selector: "app-authenticated",
2474
standalone: true,
25-
imports: [CommonModule, RouterOutlet, HeaderComponent],
75+
imports: [CommonModule, RouterModule],
2676
template: `
27-
<app-header></app-header>
28-
<div class="app-container">
29-
<router-outlet></router-outlet>
77+
<div class="max-w-sm mx-auto pt-36 space-y-6 pb-36">
78+
<div class="border border-neutral-200 dark:border-neutral-800 rounded-md p-4 space-y-4">
79+
<h1 class="text-md font-medium">Welcome, {{ user().displayName || user().email || user().phoneNumber }}</h1>
80+
@if (user().email) {
81+
@if (user().emailVerified) {
82+
<div class="text-green-500">Email verified</div>
83+
} @else {
84+
<button class="bg-red-500 text-white px-3 py-1.5 rounded text-sm" (click)="verifyEmail()">
85+
Verify Email &rarr;
86+
</button>
87+
}
88+
}
89+
<hr class="opacity-30" />
90+
<h2 class="text-sm font-medium">Multi-factor Authentication</h2>
91+
@for (factor of mfaFactors(); track factor.factorId) {
92+
<div>{{ factor.factorId }} - {{ factor.displayName }}</div>
93+
}
94+
<button class="bg-blue-500 text-white px-3 py-1.5 rounded text-sm" (click)="navigateToMfa()">
95+
Add MFA Factor &rarr;
96+
</button>
97+
<hr class="opacity-30" />
98+
<button class="bg-blue-500 text-white px-3 py-1.5 rounded text-sm" (click)="signOut()">Sign Out &rarr;</button>
99+
</div>
30100
</div>
31101
`,
32-
styles: [
33-
`
34-
.app-container {
35-
max-width: 1200px;
36-
margin: 0 auto;
37-
}
38-
39-
:host {
40-
display: block;
41-
min-height: 100vh;
42-
background-color: #f9fafb;
43-
font-family:
44-
system-ui,
45-
-apple-system,
46-
BlinkMacSystemFont,
47-
"Segoe UI",
48-
Roboto,
49-
sans-serif;
50-
}
51-
`,
52-
],
53102
})
54-
export class AppComponent {
55-
title = "Firebase UI Angular Example";
103+
export class AuthenticatedAppComponent {
104+
user = input.required<User>();
105+
private auth = inject(Auth);
106+
private router = inject(Router);
107+
108+
mfaFactors = computed(() => {
109+
const mfa = multiFactor(this.user());
110+
return mfa.enrolledFactors;
111+
});
112+
113+
async verifyEmail() {
114+
try {
115+
await sendEmailVerification(this.user());
116+
alert("Email verification sent, please check your email");
117+
} catch (error) {
118+
console.error(error);
119+
alert("Error sending email verification, check console");
120+
}
121+
}
122+
123+
navigateToMfa() {
124+
this.router.navigate(["/screens/mfa-enrollment-screen"]);
125+
}
126+
127+
async signOut() {
128+
await signOut(this.auth);
129+
}
56130
}
131+
132+
@Component({
133+
selector: "app-root",
134+
standalone: true,
135+
imports: [CommonModule, RouterModule, ThemeToggleComponent, PirateToggleComponent],
136+
templateUrl: "./app.component.html",
137+
})
138+
export class AppComponent {}

examples/angular/src/app/app.routes.server.ts

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,47 +24,63 @@ export const serverRoutes: ServerRoute[] = [
2424
},
2525
/** Static auth demos - good for SSG as they showcase Firebase UI components */
2626
{
27-
path: "sign-in",
27+
path: "screens/sign-in-auth-screen",
2828
renderMode: RenderMode.Prerender,
2929
},
3030
{
31-
path: "oauth",
31+
path: "screens/oauth-screen",
3232
renderMode: RenderMode.Prerender,
3333
},
3434
/** Interactive auth routes - better as CSR for user interaction */
3535
{
36-
path: "sign-up",
36+
path: "screens/sign-up-auth-screen",
3737
renderMode: RenderMode.Client,
3838
},
3939
{
40-
path: "forgot-password",
40+
path: "screens/forgot-password-auth-screen",
4141
renderMode: RenderMode.Client,
4242
},
4343
/** Dynamic auth routes - good for SSR as they may need server-side data */
4444
{
45-
path: "email-link",
45+
path: "screens/email-link-auth-screen",
4646
renderMode: RenderMode.Server,
4747
},
4848
{
49-
path: "email-link-oauth",
49+
path: "screens/email-link-auth-screen-w-oauth",
5050
renderMode: RenderMode.Server,
5151
},
5252
{
53-
path: "phone",
53+
path: "screens/phone-auth-screen",
5454
renderMode: RenderMode.Server,
5555
},
5656
{
57-
path: "phone-oauth",
57+
path: "screens/phone-auth-screen-w-oauth",
5858
renderMode: RenderMode.Server,
5959
},
6060
{
61-
path: "sign-in-oauth",
61+
path: "screens/sign-in-auth-screen-w-oauth",
6262
renderMode: RenderMode.Server,
6363
},
6464
{
65-
path: "sign-up-oauth",
65+
path: "screens/sign-up-auth-screen-w-oauth",
6666
renderMode: RenderMode.Server,
6767
},
68+
{
69+
path: "screens/sign-in-auth-screen-w-handlers",
70+
renderMode: RenderMode.Client,
71+
},
72+
{
73+
path: "screens/sign-up-auth-screen-w-handlers",
74+
renderMode: RenderMode.Client,
75+
},
76+
{
77+
path: "screens/forgot-password-auth-screen-w-handlers",
78+
renderMode: RenderMode.Client,
79+
},
80+
{
81+
path: "screens/mfa-enrollment-screen",
82+
renderMode: RenderMode.Client,
83+
},
6884
/** All other routes will be rendered on the server (SSR) */
6985
{
7086
path: "**",

examples/angular/src/app/app.routes.ts

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

1717
import { type Routes } from "@angular/router";
18+
import { routes as routeConfigs, hiddenRoutes } from "./routes";
19+
import { ScreenRouteLayoutComponent } from "./components/screen-route-layout/screen-route-layout.component";
20+
21+
const allRoutes = [...routeConfigs, ...hiddenRoutes];
1822

1923
export const routes: Routes = [
2024
{
2125
path: "",
2226
loadComponent: () => import("./home").then((m) => m.HomeComponent),
2327
},
2428
{
25-
path: "email-link",
26-
loadComponent: () => import("./auth/email-link").then((m) => m.EmailLinkComponent),
27-
},
28-
{
29-
path: "email-link-oauth",
30-
loadComponent: () => import("./auth/email-link-oauth").then((m) => m.EmailLinkOAuthComponent),
31-
},
32-
{
33-
path: "forgot-password",
34-
loadComponent: () => import("./auth/forgot-password").then((m) => m.ForgotPasswordComponent),
35-
},
36-
{
37-
path: "oauth",
38-
loadComponent: () => import("./auth/oauth").then((m) => m.OAuthComponent),
39-
},
40-
{
41-
path: "phone",
42-
loadComponent: () => import("./auth/phone").then((m) => m.PhoneComponent),
43-
},
44-
{
45-
path: "phone-oauth",
46-
loadComponent: () => import("./auth/phone-oauth").then((m) => m.PhoneOAuthComponent),
47-
},
48-
{
49-
path: "sign-in",
50-
loadComponent: () => import("./auth/sign-in").then((m) => m.SignInComponent),
51-
},
52-
{
53-
path: "sign-in-oauth",
54-
loadComponent: () => import("./auth/sign-in-oauth").then((m) => m.SignInOAuthComponent),
55-
},
56-
{
57-
path: "sign-up",
58-
loadComponent: () => import("./auth/sign-up").then((m) => m.SignUpComponent),
59-
},
60-
{
61-
path: "sign-up-oauth",
62-
loadComponent: () => import("./auth/sign-up-oauth").then((m) => m.SignUpOAuthComponent),
29+
path: "screens",
30+
component: ScreenRouteLayoutComponent,
31+
children: allRoutes.map((route) => ({
32+
path: route.path.replace(/^\/screens\//, ""),
33+
loadComponent: route.loadComponent,
34+
})),
6335
},
6436
{
6537
path: "**",

0 commit comments

Comments
 (0)