Skip to content

Commit 9d14645

Browse files
committed
Merge branch '@invertase/align-update-angular' of https://github.com/firebase/firebaseui-web into @invertase/phone-auth-screen-update
2 parents 79ea067 + b2cf40f commit 9d14645

File tree

4 files changed

+234
-174
lines changed

4 files changed

+234
-174
lines changed

packages/angular/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@
2727
},
2828
"dependencies": {
2929
"@firebase-ui/core": "workspace:*",
30+
"@tanstack/angular-form": "^1.21.1",
3031
"@firebase-ui/styles": "workspace:*",
31-
"@tanstack/angular-form": "^1.1.0",
3232
"tslib": "^2.8.1",
3333
"clsx": "^2.1.1",
3434
"tailwind-merge": "^3.0.1",

packages/angular/src/lib/auth/forms/sign-in-auth-form/sign-in-auth-form.component.ts

Lines changed: 62 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -14,91 +14,68 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { Component, EventEmitter, Output } from "@angular/core";
17+
import { Component, EventEmitter, Output, OnInit } from "@angular/core";
1818
import { CommonModule } from "@angular/common";
19-
import { injectForm, TanStackField } from "@tanstack/angular-form";
19+
import { injectForm, TanStackField, TanStackAppField } from "@tanstack/angular-form";
2020
import { injectSignInAuthFormSchema, injectTranslation, injectUI } from "../../../provider";
21-
import { ButtonComponent } from "../../../components/button/button.component";
2221
import { TermsAndPrivacyComponent } from "../../../components/terms-and-privacy/terms-and-privacy.component";
2322
import { FirebaseUIError, signInWithEmailAndPassword } from "@firebase-ui/core";
2423

24+
import {
25+
FormInputComponent,
26+
FormSubmitComponent,
27+
FormErrorMessageComponent,
28+
} from "../../../components/form/form.component";
29+
import { UserCredential } from "firebase/auth";
30+
2531
@Component({
2632
selector: "fui-sign-in-auth-form",
2733
standalone: true,
28-
imports: [CommonModule, TanStackField, ButtonComponent, TermsAndPrivacyComponent],
34+
imports: [
35+
CommonModule,
36+
TanStackField,
37+
TanStackAppField,
38+
TermsAndPrivacyComponent,
39+
FormInputComponent,
40+
FormSubmitComponent,
41+
FormErrorMessageComponent,
42+
],
2943
template: `
3044
<form (submit)="handleSubmit($event)" class="fui-form">
3145
<fieldset>
32-
<ng-container [tanstackField]="form" name="email" #email="field">
33-
<label [for]="email.api.name">
34-
<span>{{ emailLabel | async }}</span>
35-
<input
36-
type="email"
37-
[id]="email.api.name"
38-
[name]="email.api.name"
39-
[value]="email.api.state.value"
40-
(blur)="email.api.handleBlur()"
41-
(input)="email.api.handleChange($any($event).target.value)"
42-
[attr.aria-invalid]="!!email.api.state.meta.errors.length"
43-
/>
44-
<span role="alert" aria-live="polite" class="fui-form__error" *ngIf="!!email.api.state.meta.errors.length">
45-
{{ email.api.state.meta.errors.join(", ") }}
46-
</span>
47-
</label>
48-
</ng-container>
46+
<fui-form-input
47+
name="email"
48+
tanstack-app-field
49+
[tanstackField]="form"
50+
label="Email Label TODO"
51+
></fui-form-input>
4952
</fieldset>
5053
<fieldset>
51-
<ng-container [tanstackField]="form" name="password" #password="field">
52-
<label [for]="password.api.name">
53-
<span class="flex">
54-
<span class="flex-grow">{{ passwordLabel() }}</span>
55-
@if(forgotPassword) {
56-
<button type="button" (click)="forgotPassword.emit()" class="fui-form__action">
57-
{{ forgotPasswordLabel() }}
58-
</button>
59-
}
60-
</span>
61-
<input
62-
type="password"
63-
[id]="password.api.name"
64-
[name]="password.api.name"
65-
[value]="password.api.state.value"
66-
(blur)="password.api.handleBlur()"
67-
(input)="password.api.handleChange($any($event).target.value)"
68-
[attr.aria-invalid]="!!password.api.state.meta.errors.length"
69-
/>
70-
<span
71-
role="alert"
72-
aria-live="polite"
73-
class="fui-form__error"
74-
*ngIf="!!password.api.state.meta.errors.length"
75-
>
76-
{{ password.api.state.meta.errors.join(", ") }}
77-
</span>
78-
</label>
79-
</ng-container>
54+
<fui-form-input name="password" tanstack-app-field [tanstackField]="form" label="Password Label TODO">
55+
@if (forgotPassword) {
56+
<button fui-form-action (click)="forgotPassword.emit()">
57+
{{ forgotPasswordLabel() }}
58+
</button>
59+
}
60+
</fui-form-input>
8061
</fieldset>
8162
8263
<fui-terms-and-privacy></fui-terms-and-privacy>
8364
8465
<fieldset>
85-
<button fui-button type="submit">
66+
<fui-form-submit>
8667
{{ signInLabel() }}
87-
</button>
88-
<div class="fui-form__error" *ngIf="formError">{{ formError }}</div>
68+
</fui-form-submit>
69+
<fui-form-error-message></fui-form-error-message>
8970
</fieldset>
9071
91-
@if(register) {
92-
<div class="flex justify-center items-center">
93-
<button type="button" (click)="register.emit()" class="fui-form__action">
94-
{{ noAccountLabel() }} {{ registerLabel() }}
95-
</button>
96-
</div>
72+
@if (register) {
73+
<button fui-form-action (click)="register.emit()">{{ noAccountLabel() }} {{ registerLabel() }}</button>
9774
}
9875
</form>
9976
`,
10077
})
101-
export class SignInAuthFormComponent {
78+
export class SignInAuthFormComponent implements OnInit {
10279
private ui = injectUI();
10380
private formSchema = injectSignInAuthFormSchema();
10481

@@ -112,6 +89,7 @@ export class SignInAuthFormComponent {
11289

11390
@Output() forgotPassword = new EventEmitter<void>();
11491
@Output() register = new EventEmitter<void>();
92+
@Output() signIn?: EventEmitter<UserCredential>;
11593

11694
formError: string | null = null;
11795

@@ -120,59 +98,32 @@ export class SignInAuthFormComponent {
12098
email: "",
12199
password: "",
122100
},
123-
validators: {
124-
onBlur: this.formSchema(),
125-
onSubmit: this.formSchema(),
126-
},
127-
}) as any; // TODO(ehesp): Fix this - types go too deep
128-
129-
async handleSubmit(event: SubmitEvent) {
130-
event.preventDefault();
131-
event.stopPropagation();
132-
133-
const email = this.form.state.values.email;
134-
const password = this.form.state.values.password;
101+
});
135102

136-
if (!email || !password) {
137-
return;
138-
}
139-
140-
await this.validateAndSignIn(email, password);
103+
handleSubmit(event: SubmitEvent) {
104+
event.preventDefault()
105+
event.stopPropagation()
106+
this.form.handleSubmit()
141107
}
142108

143-
async validateAndSignIn(email: string, password: string) {
144-
try {
145-
const validationResult = this.formSchema().safeParse({
146-
email,
147-
password,
148-
});
149-
150-
if (!validationResult.success) {
151-
const validationErrors = validationResult.error.format();
152-
153-
if (validationErrors.email?._errors?.length) {
154-
this.formError = validationErrors.email._errors[0];
155-
return;
156-
}
157-
158-
if (validationErrors.password?._errors?.length) {
159-
this.formError = validationErrors.password._errors[0];
160-
return;
161-
}
162-
163-
this.formError = this.unknownErrorLabel();
164-
return;
165-
}
166-
167-
this.formError = null;
168-
await signInWithEmailAndPassword(this.ui(), email, password);
169-
} catch (error) {
170-
if (error instanceof FirebaseUIError) {
171-
this.formError = error.message;
172-
return;
173-
}
174-
175-
this.formError = this.unknownErrorLabel();
176-
}
109+
ngOnInit() {
110+
this.form.update({
111+
validators: {
112+
onBlur: this.formSchema(),
113+
onSubmit: this.formSchema(),
114+
onSubmitAsync: async ({ value }) => {
115+
try {
116+
const credential = await signInWithEmailAndPassword(this.ui(), value.email, value.password);
117+
this.signIn?.emit(credential);
118+
} catch (error) {
119+
if (error instanceof FirebaseUIError) {
120+
return error.message;
121+
}
122+
123+
return this.unknownErrorLabel();
124+
}
125+
},
126+
},
127+
});
177128
}
178129
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import { Component, HostBinding, input, Input } from '@angular/core'
2+
import { AnyFieldApi, injectField, injectForm } from '@tanstack/angular-form'
3+
import { cn } from '../../utils';
4+
import { ButtonComponent } from '../button/button.component';
5+
6+
@Component({
7+
selector: 'fui-form-metadata',
8+
standalone: true,
9+
template: `
10+
@if(field.state.meta.isTouched && field.state.meta.errors.length > 0) {
11+
<div>
12+
<div role="alert" aria-live="polite" class="fui-form__error">
13+
{{ field.state.meta.errors.join(", ") }}
14+
</div>
15+
</div>
16+
}
17+
`,
18+
})
19+
export class FormMetadataComponent {
20+
@Input() field: AnyFieldApi;
21+
}
22+
23+
@Component({
24+
selector: 'fui-form-input',
25+
standalone: true,
26+
imports: [FormMetadataComponent],
27+
template: `
28+
<label [for]="field.api.name">
29+
<span>{{ label() }}</span>
30+
<input
31+
[attr.aria-invalid]="field.api.state.meta.isTouched && field.api.state.meta.errors.length > 0"
32+
[id]="field.api.name"
33+
[name]="field.api.name"
34+
[value]="field.api.state.value"
35+
(blur)="field.api.handleBlur()"
36+
(input)="field.api.handleChange($any($event).target.value)"
37+
/>
38+
<ng-content></ng-content>
39+
<fui-form-metadata [field]="field"></fui-form-metadata>
40+
</label>
41+
`,
42+
})
43+
export class FormInputComponent {
44+
field = injectField<string>()
45+
46+
label = input.required<string>();
47+
}
48+
49+
@Component({
50+
selector: 'button[fui-form-action]',
51+
standalone: true,
52+
template: `
53+
<ng-content></ng-content>
54+
`,
55+
})
56+
export class FormActionComponent {
57+
@Input()
58+
@HostBinding("class")
59+
className: string = "";
60+
61+
@HostBinding("attr.class")
62+
get getButtonClasses(): string {
63+
return cn("fui-form__action", this.className);
64+
}
65+
66+
@HostBinding('attr.type')
67+
readonly type = 'button';
68+
69+
field = injectField<string>()
70+
}
71+
72+
@Component({
73+
selector: 'fui-form-submit',
74+
standalone: true,
75+
imports: [ButtonComponent],
76+
template: `
77+
<button fui-button [class]="buttonClasses" [disabled]="isSubmitting">
78+
<ng-content></ng-content>
79+
</button>
80+
`,
81+
})
82+
export class FormSubmitComponent {
83+
@Input()
84+
className: string = "";
85+
86+
@HostBinding('attr.type')
87+
readonly type = 'submit';
88+
89+
form = injectForm()
90+
91+
get buttonClasses(): string {
92+
return cn("fui-form__action", this.className);
93+
}
94+
95+
get isSubmitting(): boolean {
96+
return this.form.state.isSubmitting;
97+
}
98+
}
99+
100+
@Component({
101+
selector: 'fui-form-error-message',
102+
standalone: true,
103+
template: `
104+
@if (form.state.errorMap.onSubmit) {
105+
<div class="fui-form__error">
106+
{{ form.state.errorMap.onSubmit.toString() }}
107+
</div>
108+
}
109+
`,
110+
})
111+
export class FormErrorMessageComponent {
112+
form = injectForm()
113+
}

0 commit comments

Comments
 (0)