@sneat/auth-ui 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm2022/index.js +4 -0
- package/esm2022/index.js.map +1 -0
- package/esm2022/lib/components/auth-menu-item/auth-menu-item.component.js +82 -0
- package/esm2022/lib/components/auth-menu-item/auth-menu-item.component.js.map +1 -0
- package/esm2022/lib/components/index.js +4 -0
- package/esm2022/lib/components/index.js.map +1 -0
- package/esm2022/lib/components/user-auth-providers/user-auth-accounts.component.js +83 -0
- package/esm2022/lib/components/user-auth-providers/user-auth-accounts.component.js.map +1 -0
- package/esm2022/lib/components/user-auth-providers/user-auth-provider-status.js +118 -0
- package/esm2022/lib/components/user-auth-providers/user-auth-provider-status.js.map +1 -0
- package/esm2022/lib/components/user-required-fields/index.js +3 -0
- package/esm2022/lib/components/user-required-fields/index.js.map +1 -0
- package/esm2022/lib/components/user-required-fields/user-required-fields-modal.component.js +111 -0
- package/esm2022/lib/components/user-required-fields/user-required-fields-modal.component.js.map +1 -0
- package/esm2022/lib/components/user-required-fields/user-required-fields.service.js +29 -0
- package/esm2022/lib/components/user-required-fields/user-required-fields.service.js.map +1 -0
- package/esm2022/lib/pages/login-page/email-login-form/email-login-form.component.js +263 -0
- package/esm2022/lib/pages/login-page/email-login-form/email-login-form.component.js.map +1 -0
- package/esm2022/lib/pages/login-page/login-page.component.js +143 -0
- package/esm2022/lib/pages/login-page/login-page.component.js.map +1 -0
- package/esm2022/lib/pages/login-page/login-with-telegram.component.js +93 -0
- package/esm2022/lib/pages/login-page/login-with-telegram.component.js.map +1 -0
- package/esm2022/lib/pages/login-page/sneat-auth-with-telegram.service.js +31 -0
- package/esm2022/lib/pages/login-page/sneat-auth-with-telegram.service.js.map +1 -0
- package/esm2022/lib/pages/sign-in-from-email-link/sign-in-from-email-link-page.component.js +52 -0
- package/esm2022/lib/pages/sign-in-from-email-link/sign-in-from-email-link-page.component.js.map +1 -0
- package/esm2022/lib/pages/sneat-auth-routing.module.js +27 -0
- package/esm2022/lib/pages/sneat-auth-routing.module.js.map +1 -0
- package/esm2022/lib/pipes/person-names.pipe.js +35 -0
- package/esm2022/lib/pipes/person-names.pipe.js.map +1 -0
- package/esm2022/sneat-auth-ui.js +5 -0
- package/esm2022/sneat-auth-ui.js.map +1 -0
- package/lib/components/auth-menu-item/auth-menu-item.component.d.ts +24 -0
- package/lib/components/user-auth-providers/user-auth-accounts.component.d.ts +17 -0
- package/lib/components/user-auth-providers/user-auth-provider-status.d.ts +31 -0
- package/lib/components/user-required-fields/user-required-fields-modal.component.d.ts +31 -0
- package/lib/components/user-required-fields/user-required-fields.service.d.ts +7 -0
- package/lib/pages/login-page/email-login-form/email-login-form.component.d.ts +51 -0
- package/lib/pages/login-page/login-page.component.d.ts +30 -0
- package/lib/pages/login-page/login-with-telegram.component.d.ts +17 -0
- package/lib/pages/login-page/sneat-auth-with-telegram.service.d.ts +18 -0
- package/lib/pages/sign-in-from-email-link/sign-in-from-email-link-page.component.d.ts +13 -0
- package/lib/pages/sneat-auth-routing.module.d.ts +9 -0
- package/lib/pipes/person-names.pipe.d.ts +9 -0
- package/package.json +14 -2
- package/sneat-auth-ui.d.ts +5 -0
- package/eslint.config.cjs +0 -7
- package/ng-package.json +0 -7
- package/project.json +0 -38
- package/src/lib/components/auth-menu-item/auth-menu-item.component.html +0 -59
- package/src/lib/components/auth-menu-item/auth-menu-item.component.spec.ts +0 -5
- package/src/lib/components/auth-menu-item/auth-menu-item.component.ts +0 -116
- package/src/lib/components/user-auth-providers/user-auth-accounts.component.html +0 -127
- package/src/lib/components/user-auth-providers/user-auth-accounts.component.spec.ts +0 -47
- package/src/lib/components/user-auth-providers/user-auth-accounts.component.ts +0 -109
- package/src/lib/components/user-auth-providers/user-auth-provider-status.html +0 -42
- package/src/lib/components/user-auth-providers/user-auth-provider-status.ts +0 -172
- package/src/lib/components/user-required-fields/user-required-fields-modal.component.html +0 -64
- package/src/lib/components/user-required-fields/user-required-fields-modal.component.spec.ts +0 -45
- package/src/lib/components/user-required-fields/user-required-fields-modal.component.ts +0 -141
- package/src/lib/components/user-required-fields/user-required-fields.service.spec.ts +0 -335
- package/src/lib/components/user-required-fields/user-required-fields.service.ts +0 -23
- package/src/lib/pages/login-page/email-login-form/email-login-form.component.html +0 -165
- package/src/lib/pages/login-page/email-login-form/email-login-form.component.spec.ts +0 -59
- package/src/lib/pages/login-page/email-login-form/email-login-form.component.ts +0 -356
- package/src/lib/pages/login-page/index.ts +0 -3
- package/src/lib/pages/login-page/login-page.component.html +0 -146
- package/src/lib/pages/login-page/login-page.component.spec.ts +0 -5
- package/src/lib/pages/login-page/login-page.component.ts +0 -209
- package/src/lib/pages/login-page/login-with-telegram.component.spec.ts +0 -42
- package/src/lib/pages/login-page/login-with-telegram.component.ts +0 -82
- package/src/lib/pages/login-page/sneat-auth-with-telegram.service.spec.ts +0 -31
- package/src/lib/pages/login-page/sneat-auth-with-telegram.service.ts +0 -56
- package/src/lib/pages/sign-in-from-email-link/sign-in-from-email-link-page.component.html +0 -35
- package/src/lib/pages/sign-in-from-email-link/sign-in-from-email-link-page.component.spec.ts +0 -43
- package/src/lib/pages/sign-in-from-email-link/sign-in-from-email-link-page.component.ts +0 -70
- package/src/lib/pages/sneat-auth-routing.module.ts +0 -25
- package/src/lib/pipes/person-names.pipe.spec.ts +0 -317
- package/src/lib/pipes/person-names.pipe.ts +0 -31
- package/src/test-setup.ts +0 -3
- package/tsconfig.json +0 -13
- package/tsconfig.lib.json +0 -19
- package/tsconfig.lib.prod.json +0 -7
- package/tsconfig.spec.json +0 -31
- package/vite.config.mts +0 -10
- /package/{src/index.ts → index.d.ts} +0 -0
- /package/{src/lib/components/index.ts → lib/components/index.d.ts} +0 -0
- /package/{src/lib/components/user-required-fields/index.ts → lib/components/user-required-fields/index.d.ts} +0 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Injectable, inject } from '@angular/core';
|
|
2
|
+
import { ModalController } from '@ionic/angular/standalone';
|
|
3
|
+
import { UserRequiredFieldsModalComponent } from './user-required-fields-modal.component';
|
|
4
|
+
import * as i0 from "@angular/core";
|
|
5
|
+
export class UserRequiredFieldsService {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.modalController = inject(ModalController);
|
|
8
|
+
}
|
|
9
|
+
async open() {
|
|
10
|
+
const modal = await this.modalController.create({
|
|
11
|
+
component: UserRequiredFieldsModalComponent,
|
|
12
|
+
});
|
|
13
|
+
await modal.present();
|
|
14
|
+
return new Promise((resolve, reject) => {
|
|
15
|
+
modal
|
|
16
|
+
.onDidDismiss()
|
|
17
|
+
.then((value) => {
|
|
18
|
+
resolve(!!value);
|
|
19
|
+
})
|
|
20
|
+
.catch(reject);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: UserRequiredFieldsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
24
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: UserRequiredFieldsService }); }
|
|
25
|
+
}
|
|
26
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: UserRequiredFieldsService, decorators: [{
|
|
27
|
+
type: Injectable
|
|
28
|
+
}] });
|
|
29
|
+
//# sourceMappingURL=user-required-fields.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user-required-fields.service.js","sourceRoot":"","sources":["../../../../../../../../libs/auth/ui/src/lib/components/user-required-fields/user-required-fields.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,EAAE,gCAAgC,EAAE,MAAM,wCAAwC,CAAC;;AAG1F,MAAM,OAAO,yBAAyB;IADtC;QAEmB,oBAAe,GAAG,MAAM,CAAC,eAAe,CAAC,CAAC;KAgB5D;IAdQ,KAAK,CAAC,IAAI;QACf,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC;YAC9C,SAAS,EAAE,gCAAgC;SAC5C,CAAC,CAAC;QACH,MAAM,KAAK,CAAC,OAAO,EAAE,CAAC;QACtB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,KAAK;iBACF,YAAY,EAAE;iBACd,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE;gBACd,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;YACnB,CAAC,CAAC;iBACD,KAAK,CAAC,MAAM,CAAC,CAAC;QACnB,CAAC,CAAC,CAAC;IACL,CAAC;8GAhBU,yBAAyB;kHAAzB,yBAAyB;;2FAAzB,yBAAyB;kBADrC,UAAU","sourcesContent":["import { Injectable, inject } from '@angular/core';\nimport { ModalController } from '@ionic/angular/standalone';\nimport { UserRequiredFieldsModalComponent } from './user-required-fields-modal.component';\n\n@Injectable()\nexport class UserRequiredFieldsService {\n private readonly modalController = inject(ModalController);\n\n public async open(): Promise<boolean> {\n const modal = await this.modalController.create({\n component: UserRequiredFieldsModalComponent,\n });\n await modal.present();\n return new Promise((resolve, reject) => {\n modal\n .onDidDismiss()\n .then((value) => {\n resolve(!!value);\n })\n .catch(reject);\n });\n }\n}\n"]}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { Component, EventEmitter, Output, ViewChild, inject, } from '@angular/core';
|
|
2
|
+
// import { getApp } from '@angular/fire/app';
|
|
3
|
+
// import { getAuth } from '@angular/fire/auth';
|
|
4
|
+
import { Auth, sendPasswordResetEmail, sendSignInLinkToEmail, } from '@angular/fire/auth';
|
|
5
|
+
import { FormsModule } from '@angular/forms';
|
|
6
|
+
import { ToastController, IonButton, IonButtons, IonCard, IonCardContent, IonIcon, IonInput, IonItem, IonItemDivider, IonLabel, IonSegment, IonSegmentButton, IonSpinner, IonText, } from '@ionic/angular/standalone';
|
|
7
|
+
import { SneatApiService } from '@sneat/api';
|
|
8
|
+
import { UserRecordService } from '@sneat/auth-core';
|
|
9
|
+
import { createSetFocusToInput } from '@sneat/ui';
|
|
10
|
+
import { AnalyticsService, APP_INFO, } from '@sneat/core';
|
|
11
|
+
import { ErrorLogger } from '@sneat/core';
|
|
12
|
+
import { RandomIdService } from '@sneat/random';
|
|
13
|
+
import { sendEmailVerification } from 'firebase/auth';
|
|
14
|
+
import { createUserWithEmailAndPassword, signInWithEmailAndPassword, } from 'firebase/auth';
|
|
15
|
+
import * as i0 from "@angular/core";
|
|
16
|
+
import * as i1 from "@angular/forms";
|
|
17
|
+
export class EmailLoginFormComponent {
|
|
18
|
+
constructor() {
|
|
19
|
+
this.appInfo = inject(APP_INFO);
|
|
20
|
+
this.analyticsService = inject(AnalyticsService);
|
|
21
|
+
this.errorLogger = inject(ErrorLogger);
|
|
22
|
+
this.toastController = inject(ToastController);
|
|
23
|
+
this.afAuth = inject(Auth);
|
|
24
|
+
this.randomIdService = inject(RandomIdService);
|
|
25
|
+
this.sneatApiService = inject(SneatApiService);
|
|
26
|
+
this.userRecordService = inject(UserRecordService);
|
|
27
|
+
this.sign = 'up'; // TODO: document here what 'in' & 'up' means
|
|
28
|
+
this.email = '';
|
|
29
|
+
this.password = '';
|
|
30
|
+
this.firstName = '';
|
|
31
|
+
this.lastName = '';
|
|
32
|
+
this.spaceTitle = '';
|
|
33
|
+
this.wrongPassword = false;
|
|
34
|
+
this.signingWithChange = new EventEmitter();
|
|
35
|
+
this.loggedIn = new EventEmitter();
|
|
36
|
+
this.setFocusToInput = createSetFocusToInput(this.errorLogger);
|
|
37
|
+
this.email = localStorage.getItem('emailForSignIn') || '';
|
|
38
|
+
if (this.email) {
|
|
39
|
+
this.sign = 'in';
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
get validEmail() {
|
|
43
|
+
const email = this.email, i = email?.indexOf('@');
|
|
44
|
+
return i > 0 && i < email.length - 1;
|
|
45
|
+
}
|
|
46
|
+
getFirebaseAuth() {
|
|
47
|
+
return this.afAuth; // TODO: pending https://github.com/angular/angularfire/pull/3402
|
|
48
|
+
}
|
|
49
|
+
async signUp() {
|
|
50
|
+
if (!this.firstName) {
|
|
51
|
+
// this.toaster.showToast('Full name is required');
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
// this.signingWith = 'email';
|
|
55
|
+
this.email = this.email.trim();
|
|
56
|
+
const email = this.email;
|
|
57
|
+
const firstName = this.firstName.trim();
|
|
58
|
+
const lastName = this.lastName.trim();
|
|
59
|
+
if (!email) {
|
|
60
|
+
alert('Email is a required field');
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (!firstName) {
|
|
64
|
+
alert('First name is a required field');
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (!lastName) {
|
|
68
|
+
alert('Last name is a required field');
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
this.spaceTitle = this.spaceTitle.trim();
|
|
72
|
+
const spaceTitle = this.spaceTitle;
|
|
73
|
+
if (this.appInfo.requiredSpaceType && !spaceTitle) {
|
|
74
|
+
alert('Company title is a required field');
|
|
75
|
+
this.setFocusToSpaceTitle();
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
localStorage.setItem('emailForSignIn', email);
|
|
79
|
+
const password = this.randomIdService.newRandomId({ len: 9 });
|
|
80
|
+
this.setSigningWith('email');
|
|
81
|
+
try {
|
|
82
|
+
// const auth = getAuth(getApp());
|
|
83
|
+
const auth = this.getFirebaseAuth();
|
|
84
|
+
const userCredential = await createUserWithEmailAndPassword(auth, email, password);
|
|
85
|
+
this.sendVerificationEmail(userCredential);
|
|
86
|
+
userCredential.user
|
|
87
|
+
?.getIdToken()
|
|
88
|
+
.then((token) => {
|
|
89
|
+
this.sneatApiService.setApiAuthToken(token);
|
|
90
|
+
const request = {
|
|
91
|
+
authProvider: 'password',
|
|
92
|
+
email,
|
|
93
|
+
ianaTimezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
94
|
+
names: { firstName, lastName },
|
|
95
|
+
space: this.appInfo.requiredSpaceType
|
|
96
|
+
? {
|
|
97
|
+
type: this.appInfo.requiredSpaceType,
|
|
98
|
+
title: spaceTitle,
|
|
99
|
+
}
|
|
100
|
+
: undefined,
|
|
101
|
+
};
|
|
102
|
+
this.userRecordService.initUserRecord(request).subscribe({
|
|
103
|
+
next: () => this.onLoggedIn(userCredential),
|
|
104
|
+
error: (err) => {
|
|
105
|
+
this.analyticsService.logEvent('FailedToSetUserTitle');
|
|
106
|
+
this.errorLogger.logError(err, 'Failed to set user title', {
|
|
107
|
+
feedback: false,
|
|
108
|
+
});
|
|
109
|
+
this.onLoggedIn(userCredential);
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
})
|
|
113
|
+
.catch(this.errorHandler('Failed to get Firebase ID token', 'FirebaseGetIdTokenFailed'));
|
|
114
|
+
}
|
|
115
|
+
catch (e) {
|
|
116
|
+
this.handleError(e, 'Failed to sign up with email', 'FailedToSignUpWithEmail');
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
keyupEnter() {
|
|
120
|
+
switch (this.sign) {
|
|
121
|
+
case 'in':
|
|
122
|
+
this.signIn();
|
|
123
|
+
break;
|
|
124
|
+
case 'up':
|
|
125
|
+
this.signUp().catch(() => this.errorHandler('Failed to sign up'));
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
signIn() {
|
|
130
|
+
this.setSigningWith('email');
|
|
131
|
+
this.email = this.email.trim();
|
|
132
|
+
this.wrongPassword = false;
|
|
133
|
+
this.saveEmailForReuse();
|
|
134
|
+
// const auth = getAuth(getApp());
|
|
135
|
+
const auth = this.getFirebaseAuth();
|
|
136
|
+
signInWithEmailAndPassword(auth, this.email, this.password)
|
|
137
|
+
.then((userCredential) => {
|
|
138
|
+
this.onLoggedIn(userCredential); // TODO: add analytics event
|
|
139
|
+
})
|
|
140
|
+
.catch(this.errorHandler('Failed to sign in with email & password', 'email'));
|
|
141
|
+
}
|
|
142
|
+
saveEmailForReuse() {
|
|
143
|
+
localStorage.setItem('emailForSignIn', this.email);
|
|
144
|
+
}
|
|
145
|
+
sendSignInLink() {
|
|
146
|
+
this.setSigningWith('emailLink');
|
|
147
|
+
this.email = this.email.trim();
|
|
148
|
+
this.saveEmailForReuse();
|
|
149
|
+
sendSignInLinkToEmail(this.afAuth, this.email, {
|
|
150
|
+
// url: 'https://dailyscrum.app/pwa/sign-in',
|
|
151
|
+
url: document.baseURI + 'sign-in-from-email-link',
|
|
152
|
+
handleCodeInApp: true,
|
|
153
|
+
})
|
|
154
|
+
.then(() => {
|
|
155
|
+
this.showToast(`Sign-in link has been sent to email: ${this.email}`);
|
|
156
|
+
this.setSigningWith(undefined);
|
|
157
|
+
})
|
|
158
|
+
.catch(this.errorHandler('Failed to send sign in link to email', 'FailedToSendSignInLinkToEmail'));
|
|
159
|
+
}
|
|
160
|
+
showToast(message) {
|
|
161
|
+
this.toastController
|
|
162
|
+
.create({
|
|
163
|
+
message,
|
|
164
|
+
position: 'middle',
|
|
165
|
+
keyboardClose: true,
|
|
166
|
+
duration: 3000,
|
|
167
|
+
color: 'tertiary',
|
|
168
|
+
icon: 'send-outline',
|
|
169
|
+
buttons: [{ icon: 'close', role: 'cancel' }],
|
|
170
|
+
})
|
|
171
|
+
.then((toast) => {
|
|
172
|
+
toast
|
|
173
|
+
.present()
|
|
174
|
+
.catch(this.errorLogger.logErrorHandler('Failed to present toast about password reset email sent success'));
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
resetPassword() {
|
|
178
|
+
this.setSigningWith('resetPassword');
|
|
179
|
+
sendPasswordResetEmail(this.afAuth, this.email)
|
|
180
|
+
.then(() => {
|
|
181
|
+
this.setSigningWith(undefined);
|
|
182
|
+
this.showToast(`Password reset link has been sent to email: ${this.email}`);
|
|
183
|
+
})
|
|
184
|
+
.catch(this.errorHandler('Failed to send password reset email', 'FailedToSendPasswordResetEmail'));
|
|
185
|
+
}
|
|
186
|
+
sendVerificationEmail(userCredential) {
|
|
187
|
+
setTimeout(async () => {
|
|
188
|
+
try {
|
|
189
|
+
await sendEmailVerification(userCredential.user);
|
|
190
|
+
}
|
|
191
|
+
catch (e) {
|
|
192
|
+
this.handleError(e, 'Failed to send verification email');
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
onLoggedIn(userCredential) {
|
|
197
|
+
this.loggedIn.emit(userCredential);
|
|
198
|
+
}
|
|
199
|
+
errorHandler(m, eventName, eventParams) {
|
|
200
|
+
return (err) => this.handleError(err, m, eventName, eventParams);
|
|
201
|
+
}
|
|
202
|
+
handleError(err, m, eventName, eventParams) {
|
|
203
|
+
this.setSigningWith(undefined);
|
|
204
|
+
if (eventName) {
|
|
205
|
+
this.analyticsService.logEvent(eventName, eventParams);
|
|
206
|
+
}
|
|
207
|
+
if (err.code === 'auth/wrong-password') {
|
|
208
|
+
this.wrongPassword = true;
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
this.errorLogger.logError(err, m, {
|
|
212
|
+
report: !err.code,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
setSigningWith(signingWith) {
|
|
216
|
+
this.signingWith = signingWith;
|
|
217
|
+
this.signingWithChange.emit(signingWith);
|
|
218
|
+
}
|
|
219
|
+
signChanged() {
|
|
220
|
+
this.setFocusToEmail();
|
|
221
|
+
}
|
|
222
|
+
ionViewDidEnter() {
|
|
223
|
+
this.setFocusToEmail();
|
|
224
|
+
}
|
|
225
|
+
setFocusToEmail() {
|
|
226
|
+
this.setFocusToInput(undefined /*this.emailInput*/);
|
|
227
|
+
}
|
|
228
|
+
setFocusToSpaceTitle() {
|
|
229
|
+
this.setFocusToInput(undefined /*this.spaceTitleInput*/);
|
|
230
|
+
}
|
|
231
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: EmailLoginFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
232
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: EmailLoginFormComponent, isStandalone: true, selector: "sneat-email-login-form", outputs: { signingWithChange: "signingWithChange", loggedIn: "loggedIn" }, viewQueries: [{ propertyName: "emailInput", first: true, predicate: ["emailInput"], descendants: true, static: true }, { propertyName: "spaceTitleInput", first: true, predicate: ["spaceTitleInput"], descendants: true }], ngImport: i0, template: "<div class=\"ion-padding-horizontal\"></div>\n<ion-card>\n <ion-item-divider color=\"light\" class=\"ion-no-padding\">\n <ion-segment [(ngModel)]=\"sign\" (ionChange)=\"signChanged()\">\n <ion-segment-button value=\"up\">\n <ion-icon name=\"person-add\" class=\"ion-margin-end\"></ion-icon>\n <ion-label>Sign up</ion-label>\n </ion-segment-button>\n <ion-segment-button value=\"in\">\n <ion-icon name=\"enter-outline\" />\n <ion-label>Sign in</ion-label>\n </ion-segment-button>\n </ion-segment>\n </ion-item-divider>\n <!--\t<ion-item-divider color=\"light\">-->\n <!--\t\t<ion-label color=\"medium\" style=\"text-align: center; width: 100%\">-->\n <!--\t\t\tOr login with email-->\n <!--\t\t</ion-label>-->\n <!--\t</ion-item-divider>-->\n <ion-card-content class=\"ion-no-padding\">\n <ion-item>\n <ion-label class=\"ion-margin-end\">\n <ion-icon name=\"mail-outline\" />\n </ion-label>\n <ion-input\n [disabled]=\"!!signingWith\"\n [(ngModel)]=\"email\"\n email\n type=\"email\"\n name=\"email\"\n id=\"email\"\n required\n placeholder=\"my@work-or-personal.email\"\n (keyup.enter)=\"keyupEnter()\"\n />\n </ion-item>\n @switch (sign) {\n @case (\"up\") {\n <ion-item>\n <ion-label class=\"ion-margin-end\">\n <ion-icon name=\"person-add-outline\" />\n </ion-label>\n <ion-input\n name=\"first_name\"\n placeholder=\"First name\"\n [(ngModel)]=\"firstName\"\n type=\"text\"\n required\n (keyup.enter)=\"keyupEnter()\"\n [disabled]=\"!!signingWith\"\n />\n <ion-input\n name=\"last_name\"\n placeholder=\"Last name\"\n [(ngModel)]=\"lastName\"\n type=\"text\"\n required\n (keyup.enter)=\"keyupEnter()\"\n [disabled]=\"!!signingWith\"\n />\n </ion-item>\n @if (appInfo.requiredSpaceType === \"company\") {\n <ion-item>\n <ion-label position=\"stacked\">Company name</ion-label>\n <ion-input\n #teamTitleInput\n placeholder=\"required\"\n required\n [(ngModel)]=\"spaceTitle\"\n />\n </ion-item>\n }\n }\n @case (\"in\") {\n <ion-item>\n <ion-label class=\"ion-margin-end\">\n <ion-icon name=\"key-outline\" />\n </ion-label>\n <ion-input\n [(ngModel)]=\"password\"\n placeholder=\"Password\"\n type=\"password\"\n required\n (keyup.enter)=\"keyupEnter()\"\n [disabled]=\"!!signingWith\"\n />\n <ion-buttons slot=\"end\">\n <ion-button\n color=\"danger\"\n [disabled]=\"!validEmail\"\n (click)=\"resetPassword()\"\n title=\"Send email with a link to reset password \"\n >\n <ion-label>Forgot? Reset!</ion-label>\n </ion-button>\n </ion-buttons>\n </ion-item>\n @if (wrongPassword) {\n <ion-item>\n <ion-label color=\"danger\">Wrong password</ion-label>\n </ion-item>\n }\n }\n }\n </ion-card-content>\n <ion-card-content>\n @switch (sign) {\n @case (\"up\") {\n <ion-button\n [disabled]=\"!!signingWith || !validEmail || (!firstName && !lastName)\"\n (click)=\"signUp()\"\n >\n <ion-icon name=\"enter-outline\" slot=\"start\" />\n @if (signingWith === \"email\") {\n <ion-label>Signing up...</ion-label>\n <ion-spinner\n class=\"ion-margin-start\"\n slot=\"end\"\n name=\"lines-small\"\n />\n } @else {\n <ion-label>Sign up</ion-label>\n }\n </ion-button>\n }\n @case (\"in\") {\n <ion-button\n [disabled]=\"!!signingWith || !validEmail || !password\"\n (click)=\"signIn()\"\n >\n <ion-icon name=\"enter-outline\" slot=\"start\" />\n @if (signingWith === \"email\") {\n <ion-label>Signing in...</ion-label>\n <ion-spinner\n class=\"ion-margin-start\"\n slot=\"end\"\n name=\"lines-small\"\n />\n } @else {\n <ion-label>\n <b>Sign in</b> <ion-text\n color=\"light\"\n style=\"text-transform: none\"\n >with password\n </ion-text>\n </ion-label>\n }\n </ion-button>\n <ion-button disabled=\"disabled\" fill=\"clear\" color=\"medium\">\n or\n </ion-button>\n <ion-button\n size=\"small\"\n [disabled]=\"!validEmail || signingWith\"\n (click)=\"sendSignInLink()\"\n fill=\"outline\"\n color=\"tertiary\"\n >\n <ion-icon name=\"mail-outline\" slot=\"start\" />\n <ion-label>Get sign-in link</ion-label>\n </ion-button>\n }\n }\n </ion-card-content>\n</ion-card>\n", dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1.EmailValidator, selector: "[email][formControlName],[email][formControl],[email][ngModel]", inputs: ["email"] }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: IonCard, selector: "ion-card", inputs: ["button", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonItemDivider, selector: "ion-item-divider", inputs: ["color", "mode", "sticky"] }, { kind: "component", type: IonSegment, selector: "ion-segment", inputs: ["color", "disabled", "mode", "scrollable", "selectOnFocus", "swipeGesture", "value"] }, { kind: "component", type: IonSegmentButton, selector: "ion-segment-button", inputs: ["contentId", "disabled", "layout", "mode", "type", "value"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: IonCardContent, selector: "ion-card-content", inputs: ["mode"] }, { kind: "component", type: IonItem, selector: "ion-item", inputs: ["button", "color", "detail", "detailIcon", "disabled", "download", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonInput, selector: "ion-input", inputs: ["accept", "autocapitalize", "autocomplete", "autocorrect", "autofocus", "clearInput", "clearOnEdit", "color", "counter", "counterFormatter", "debounce", "disabled", "enterkeyhint", "errorText", "fill", "helperText", "inputmode", "label", "labelPlacement", "max", "maxlength", "min", "minlength", "mode", "multiple", "name", "pattern", "placeholder", "readonly", "required", "shape", "size", "spellcheck", "step", "type", "value"] }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }] }); }
|
|
233
|
+
}
|
|
234
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: EmailLoginFormComponent, decorators: [{
|
|
235
|
+
type: Component,
|
|
236
|
+
args: [{ selector: 'sneat-email-login-form', imports: [
|
|
237
|
+
FormsModule,
|
|
238
|
+
IonCard,
|
|
239
|
+
IonItemDivider,
|
|
240
|
+
IonSegment,
|
|
241
|
+
IonSegmentButton,
|
|
242
|
+
IonIcon,
|
|
243
|
+
IonLabel,
|
|
244
|
+
IonCardContent,
|
|
245
|
+
IonItem,
|
|
246
|
+
IonInput,
|
|
247
|
+
IonSpinner,
|
|
248
|
+
IonText,
|
|
249
|
+
IonButton,
|
|
250
|
+
IonButtons,
|
|
251
|
+
], template: "<div class=\"ion-padding-horizontal\"></div>\n<ion-card>\n <ion-item-divider color=\"light\" class=\"ion-no-padding\">\n <ion-segment [(ngModel)]=\"sign\" (ionChange)=\"signChanged()\">\n <ion-segment-button value=\"up\">\n <ion-icon name=\"person-add\" class=\"ion-margin-end\"></ion-icon>\n <ion-label>Sign up</ion-label>\n </ion-segment-button>\n <ion-segment-button value=\"in\">\n <ion-icon name=\"enter-outline\" />\n <ion-label>Sign in</ion-label>\n </ion-segment-button>\n </ion-segment>\n </ion-item-divider>\n <!--\t<ion-item-divider color=\"light\">-->\n <!--\t\t<ion-label color=\"medium\" style=\"text-align: center; width: 100%\">-->\n <!--\t\t\tOr login with email-->\n <!--\t\t</ion-label>-->\n <!--\t</ion-item-divider>-->\n <ion-card-content class=\"ion-no-padding\">\n <ion-item>\n <ion-label class=\"ion-margin-end\">\n <ion-icon name=\"mail-outline\" />\n </ion-label>\n <ion-input\n [disabled]=\"!!signingWith\"\n [(ngModel)]=\"email\"\n email\n type=\"email\"\n name=\"email\"\n id=\"email\"\n required\n placeholder=\"my@work-or-personal.email\"\n (keyup.enter)=\"keyupEnter()\"\n />\n </ion-item>\n @switch (sign) {\n @case (\"up\") {\n <ion-item>\n <ion-label class=\"ion-margin-end\">\n <ion-icon name=\"person-add-outline\" />\n </ion-label>\n <ion-input\n name=\"first_name\"\n placeholder=\"First name\"\n [(ngModel)]=\"firstName\"\n type=\"text\"\n required\n (keyup.enter)=\"keyupEnter()\"\n [disabled]=\"!!signingWith\"\n />\n <ion-input\n name=\"last_name\"\n placeholder=\"Last name\"\n [(ngModel)]=\"lastName\"\n type=\"text\"\n required\n (keyup.enter)=\"keyupEnter()\"\n [disabled]=\"!!signingWith\"\n />\n </ion-item>\n @if (appInfo.requiredSpaceType === \"company\") {\n <ion-item>\n <ion-label position=\"stacked\">Company name</ion-label>\n <ion-input\n #teamTitleInput\n placeholder=\"required\"\n required\n [(ngModel)]=\"spaceTitle\"\n />\n </ion-item>\n }\n }\n @case (\"in\") {\n <ion-item>\n <ion-label class=\"ion-margin-end\">\n <ion-icon name=\"key-outline\" />\n </ion-label>\n <ion-input\n [(ngModel)]=\"password\"\n placeholder=\"Password\"\n type=\"password\"\n required\n (keyup.enter)=\"keyupEnter()\"\n [disabled]=\"!!signingWith\"\n />\n <ion-buttons slot=\"end\">\n <ion-button\n color=\"danger\"\n [disabled]=\"!validEmail\"\n (click)=\"resetPassword()\"\n title=\"Send email with a link to reset password \"\n >\n <ion-label>Forgot? Reset!</ion-label>\n </ion-button>\n </ion-buttons>\n </ion-item>\n @if (wrongPassword) {\n <ion-item>\n <ion-label color=\"danger\">Wrong password</ion-label>\n </ion-item>\n }\n }\n }\n </ion-card-content>\n <ion-card-content>\n @switch (sign) {\n @case (\"up\") {\n <ion-button\n [disabled]=\"!!signingWith || !validEmail || (!firstName && !lastName)\"\n (click)=\"signUp()\"\n >\n <ion-icon name=\"enter-outline\" slot=\"start\" />\n @if (signingWith === \"email\") {\n <ion-label>Signing up...</ion-label>\n <ion-spinner\n class=\"ion-margin-start\"\n slot=\"end\"\n name=\"lines-small\"\n />\n } @else {\n <ion-label>Sign up</ion-label>\n }\n </ion-button>\n }\n @case (\"in\") {\n <ion-button\n [disabled]=\"!!signingWith || !validEmail || !password\"\n (click)=\"signIn()\"\n >\n <ion-icon name=\"enter-outline\" slot=\"start\" />\n @if (signingWith === \"email\") {\n <ion-label>Signing in...</ion-label>\n <ion-spinner\n class=\"ion-margin-start\"\n slot=\"end\"\n name=\"lines-small\"\n />\n } @else {\n <ion-label>\n <b>Sign in</b> <ion-text\n color=\"light\"\n style=\"text-transform: none\"\n >with password\n </ion-text>\n </ion-label>\n }\n </ion-button>\n <ion-button disabled=\"disabled\" fill=\"clear\" color=\"medium\">\n or\n </ion-button>\n <ion-button\n size=\"small\"\n [disabled]=\"!validEmail || signingWith\"\n (click)=\"sendSignInLink()\"\n fill=\"outline\"\n color=\"tertiary\"\n >\n <ion-icon name=\"mail-outline\" slot=\"start\" />\n <ion-label>Get sign-in link</ion-label>\n </ion-button>\n }\n }\n </ion-card-content>\n</ion-card>\n" }]
|
|
252
|
+
}], ctorParameters: () => [], propDecorators: { signingWithChange: [{
|
|
253
|
+
type: Output
|
|
254
|
+
}], loggedIn: [{
|
|
255
|
+
type: Output
|
|
256
|
+
}], emailInput: [{
|
|
257
|
+
type: ViewChild,
|
|
258
|
+
args: ['emailInput', { static: true }]
|
|
259
|
+
}], spaceTitleInput: [{
|
|
260
|
+
type: ViewChild,
|
|
261
|
+
args: ['spaceTitleInput', { static: false }]
|
|
262
|
+
}] } });
|
|
263
|
+
//# sourceMappingURL=email-login-form.component.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"email-login-form.component.js","sourceRoot":"","sources":["../../../../../../../../../libs/auth/ui/src/lib/pages/login-page/email-login-form/email-login-form.component.ts","../../../../../../../../../libs/auth/ui/src/lib/pages/login-page/email-login-form/email-login-form.component.html"],"names":[],"mappings":"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","sourcesContent":["import {\n Component,\n EventEmitter,\n Output,\n ViewChild,\n inject,\n} from '@angular/core';\n// import { getApp } from '@angular/fire/app';\n// import { getAuth } from '@angular/fire/auth';\nimport {\n Auth,\n sendPasswordResetEmail,\n sendSignInLinkToEmail,\n} from '@angular/fire/auth';\nimport { FormsModule } from '@angular/forms';\nimport {\n ToastController,\n IonButton,\n IonButtons,\n IonCard,\n IonCardContent,\n IonIcon,\n IonInput,\n IonItem,\n IonItemDivider,\n IonLabel,\n IonSegment,\n IonSegmentButton,\n IonSpinner,\n IonText,\n} from '@ionic/angular/standalone';\nimport { SneatApiService } from '@sneat/api';\nimport { IInitUserRecordRequest, UserRecordService } from '@sneat/auth-core';\nimport { createSetFocusToInput } from '@sneat/ui';\nimport {\n AnalyticsService,\n APP_INFO,\n IAnalyticsService,\n IAppInfo,\n} from '@sneat/core';\nimport { ErrorLogger, IErrorLogger } from '@sneat/core';\nimport { RandomIdService } from '@sneat/random';\nimport { UserCredential, sendEmailVerification } from 'firebase/auth';\nimport { FirebaseError } from 'firebase/app';\n\nexport type EmailFormSigningWith = 'email' | 'emailLink' | 'resetPassword';\nimport {\n createUserWithEmailAndPassword,\n signInWithEmailAndPassword,\n} from 'firebase/auth';\n\n@Component({\n selector: 'sneat-email-login-form',\n templateUrl: 'email-login-form.component.html',\n imports: [\n FormsModule,\n IonCard,\n IonItemDivider,\n IonSegment,\n IonSegmentButton,\n IonIcon,\n IonLabel,\n IonCardContent,\n IonItem,\n IonInput,\n IonSpinner,\n IonText,\n IonButton,\n IonButtons,\n ],\n})\nexport class EmailLoginFormComponent {\n readonly appInfo = inject<IAppInfo>(APP_INFO);\n private readonly analyticsService =\n inject<IAnalyticsService>(AnalyticsService);\n private readonly errorLogger = inject<IErrorLogger>(ErrorLogger);\n private readonly toastController = inject(ToastController);\n private readonly afAuth = inject(Auth);\n private readonly randomIdService = inject(RandomIdService);\n private readonly sneatApiService = inject(SneatApiService);\n private readonly userRecordService = inject(UserRecordService);\n\n protected sign: 'in' | 'up' = 'up'; // TODO: document here what 'in' & 'up' means\n protected email = '';\n protected password = '';\n protected firstName = '';\n protected lastName = '';\n protected spaceTitle = '';\n protected wrongPassword = false;\n\n protected signingWith?: EmailFormSigningWith;\n\n @Output() readonly signingWithChange = new EventEmitter<\n EmailFormSigningWith | undefined\n >();\n @Output() readonly loggedIn = new EventEmitter<UserCredential>();\n\n @ViewChild('emailInput', { static: true }) emailInput?: IonInput;\n @ViewChild('spaceTitleInput', { static: false }) spaceTitleInput?: IonInput;\n\n public readonly setFocusToInput = createSetFocusToInput(this.errorLogger);\n\n constructor() {\n this.email = localStorage.getItem('emailForSignIn') || '';\n if (this.email) {\n this.sign = 'in';\n }\n }\n\n public get validEmail(): boolean {\n const email = this.email,\n i = email?.indexOf('@');\n return i > 0 && i < email.length - 1;\n }\n\n public getFirebaseAuth(): Auth {\n return this.afAuth as unknown as Auth; // TODO: pending https://github.com/angular/angularfire/pull/3402\n }\n\n public async signUp(): Promise<void> {\n if (!this.firstName) {\n // this.toaster.showToast('Full name is required');\n return;\n }\n // this.signingWith = 'email';\n this.email = this.email.trim();\n const email = this.email;\n const firstName = this.firstName.trim();\n const lastName = this.lastName.trim();\n if (!email) {\n alert('Email is a required field');\n return;\n }\n if (!firstName) {\n alert('First name is a required field');\n return;\n }\n if (!lastName) {\n alert('Last name is a required field');\n return;\n }\n this.spaceTitle = this.spaceTitle.trim();\n const spaceTitle = this.spaceTitle;\n if (this.appInfo.requiredSpaceType && !spaceTitle) {\n alert('Company title is a required field');\n this.setFocusToSpaceTitle();\n return;\n }\n localStorage.setItem('emailForSignIn', email);\n const password = this.randomIdService.newRandomId({ len: 9 });\n\n this.setSigningWith('email');\n try {\n // const auth = getAuth(getApp());\n const auth = this.getFirebaseAuth();\n const userCredential = await createUserWithEmailAndPassword(\n auth,\n email,\n password,\n );\n this.sendVerificationEmail(userCredential);\n userCredential.user\n ?.getIdToken()\n .then((token) => {\n this.sneatApiService.setApiAuthToken(token);\n const request: IInitUserRecordRequest = {\n authProvider: 'password',\n email,\n ianaTimezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n names: { firstName, lastName },\n space: this.appInfo.requiredSpaceType\n ? {\n type: this.appInfo.requiredSpaceType,\n title: spaceTitle,\n }\n : undefined,\n };\n this.userRecordService.initUserRecord(request).subscribe({\n next: () => this.onLoggedIn(userCredential),\n error: (err) => {\n this.analyticsService.logEvent('FailedToSetUserTitle');\n this.errorLogger.logError(err, 'Failed to set user title', {\n feedback: false,\n });\n this.onLoggedIn(userCredential);\n },\n });\n })\n .catch(\n this.errorHandler(\n 'Failed to get Firebase ID token',\n 'FirebaseGetIdTokenFailed',\n ),\n );\n } catch (e) {\n this.handleError(\n e,\n 'Failed to sign up with email',\n 'FailedToSignUpWithEmail',\n );\n }\n }\n\n public keyupEnter(): void {\n switch (this.sign) {\n case 'in':\n this.signIn();\n break;\n case 'up':\n this.signUp().catch(() => this.errorHandler('Failed to sign up'));\n break;\n }\n }\n\n public signIn(): void {\n this.setSigningWith('email');\n this.email = this.email.trim();\n this.wrongPassword = false;\n this.saveEmailForReuse();\n // const auth = getAuth(getApp());\n const auth = this.getFirebaseAuth();\n signInWithEmailAndPassword(auth, this.email, this.password)\n .then((userCredential) => {\n this.onLoggedIn(userCredential); // TODO: add analytics event\n })\n .catch(\n this.errorHandler('Failed to sign in with email & password', 'email'),\n );\n }\n\n private saveEmailForReuse(): void {\n localStorage.setItem('emailForSignIn', this.email);\n }\n\n public sendSignInLink(): void {\n this.setSigningWith('emailLink');\n this.email = this.email.trim();\n this.saveEmailForReuse();\n sendSignInLinkToEmail(this.afAuth, this.email, {\n // url: 'https://dailyscrum.app/pwa/sign-in',\n url: document.baseURI + 'sign-in-from-email-link',\n handleCodeInApp: true,\n })\n .then(() => {\n this.showToast(`Sign-in link has been sent to email: ${this.email}`);\n this.setSigningWith(undefined);\n })\n .catch(\n this.errorHandler(\n 'Failed to send sign in link to email',\n 'FailedToSendSignInLinkToEmail',\n ),\n );\n }\n\n private showToast(message: string): void {\n this.toastController\n .create({\n message,\n position: 'middle',\n keyboardClose: true,\n duration: 3000,\n color: 'tertiary',\n icon: 'send-outline',\n buttons: [{ icon: 'close', role: 'cancel' }],\n })\n .then((toast) => {\n toast\n .present()\n .catch(\n this.errorLogger.logErrorHandler(\n 'Failed to present toast about password reset email sent success',\n ),\n );\n });\n }\n\n public resetPassword(): void {\n this.setSigningWith('resetPassword');\n sendPasswordResetEmail(this.afAuth, this.email)\n .then(() => {\n this.setSigningWith(undefined);\n this.showToast(\n `Password reset link has been sent to email: ${this.email}`,\n );\n })\n .catch(\n this.errorHandler(\n 'Failed to send password reset email',\n 'FailedToSendPasswordResetEmail',\n ),\n );\n }\n\n private sendVerificationEmail(userCredential: UserCredential): void {\n setTimeout(async () => {\n try {\n await sendEmailVerification(userCredential.user);\n } catch (e) {\n this.handleError(e, 'Failed to send verification email');\n }\n });\n }\n\n private onLoggedIn(userCredential: UserCredential): void {\n this.loggedIn.emit(userCredential);\n }\n\n private errorHandler(\n m: string,\n eventName?: string,\n eventParams?: Record<string, string>,\n ): (err: unknown) => void {\n return (err) => this.handleError(err, m, eventName, eventParams);\n }\n\n private handleError(\n err: unknown,\n m: string,\n eventName?: string,\n eventParams?: Record<string, string>,\n ): void {\n this.setSigningWith(undefined);\n if (eventName) {\n this.analyticsService.logEvent(eventName, eventParams);\n }\n if ((err as FirebaseError).code === 'auth/wrong-password') {\n this.wrongPassword = true;\n return;\n }\n this.errorLogger.logError(err, m, {\n report: !(err as { code: unknown }).code,\n });\n }\n\n private setSigningWith(signingWith?: EmailFormSigningWith): void {\n this.signingWith = signingWith;\n this.signingWithChange.emit(signingWith);\n }\n\n signChanged(): void {\n this.setFocusToEmail();\n }\n\n ionViewDidEnter(): void {\n this.setFocusToEmail();\n }\n\n private setFocusToEmail(): void {\n this.setFocusToInput(undefined /*this.emailInput*/);\n }\n\n private setFocusToSpaceTitle(): void {\n this.setFocusToInput(undefined /*this.spaceTitleInput*/);\n }\n}\n","<div class=\"ion-padding-horizontal\"></div>\n<ion-card>\n <ion-item-divider color=\"light\" class=\"ion-no-padding\">\n <ion-segment [(ngModel)]=\"sign\" (ionChange)=\"signChanged()\">\n <ion-segment-button value=\"up\">\n <ion-icon name=\"person-add\" class=\"ion-margin-end\"></ion-icon>\n <ion-label>Sign up</ion-label>\n </ion-segment-button>\n <ion-segment-button value=\"in\">\n <ion-icon name=\"enter-outline\" />\n <ion-label>Sign in</ion-label>\n </ion-segment-button>\n </ion-segment>\n </ion-item-divider>\n <!--\t<ion-item-divider color=\"light\">-->\n <!--\t\t<ion-label color=\"medium\" style=\"text-align: center; width: 100%\">-->\n <!--\t\t\tOr login with email-->\n <!--\t\t</ion-label>-->\n <!--\t</ion-item-divider>-->\n <ion-card-content class=\"ion-no-padding\">\n <ion-item>\n <ion-label class=\"ion-margin-end\">\n <ion-icon name=\"mail-outline\" />\n </ion-label>\n <ion-input\n [disabled]=\"!!signingWith\"\n [(ngModel)]=\"email\"\n email\n type=\"email\"\n name=\"email\"\n id=\"email\"\n required\n placeholder=\"my@work-or-personal.email\"\n (keyup.enter)=\"keyupEnter()\"\n />\n </ion-item>\n @switch (sign) {\n @case (\"up\") {\n <ion-item>\n <ion-label class=\"ion-margin-end\">\n <ion-icon name=\"person-add-outline\" />\n </ion-label>\n <ion-input\n name=\"first_name\"\n placeholder=\"First name\"\n [(ngModel)]=\"firstName\"\n type=\"text\"\n required\n (keyup.enter)=\"keyupEnter()\"\n [disabled]=\"!!signingWith\"\n />\n <ion-input\n name=\"last_name\"\n placeholder=\"Last name\"\n [(ngModel)]=\"lastName\"\n type=\"text\"\n required\n (keyup.enter)=\"keyupEnter()\"\n [disabled]=\"!!signingWith\"\n />\n </ion-item>\n @if (appInfo.requiredSpaceType === \"company\") {\n <ion-item>\n <ion-label position=\"stacked\">Company name</ion-label>\n <ion-input\n #teamTitleInput\n placeholder=\"required\"\n required\n [(ngModel)]=\"spaceTitle\"\n />\n </ion-item>\n }\n }\n @case (\"in\") {\n <ion-item>\n <ion-label class=\"ion-margin-end\">\n <ion-icon name=\"key-outline\" />\n </ion-label>\n <ion-input\n [(ngModel)]=\"password\"\n placeholder=\"Password\"\n type=\"password\"\n required\n (keyup.enter)=\"keyupEnter()\"\n [disabled]=\"!!signingWith\"\n />\n <ion-buttons slot=\"end\">\n <ion-button\n color=\"danger\"\n [disabled]=\"!validEmail\"\n (click)=\"resetPassword()\"\n title=\"Send email with a link to reset password \"\n >\n <ion-label>Forgot? Reset!</ion-label>\n </ion-button>\n </ion-buttons>\n </ion-item>\n @if (wrongPassword) {\n <ion-item>\n <ion-label color=\"danger\">Wrong password</ion-label>\n </ion-item>\n }\n }\n }\n </ion-card-content>\n <ion-card-content>\n @switch (sign) {\n @case (\"up\") {\n <ion-button\n [disabled]=\"!!signingWith || !validEmail || (!firstName && !lastName)\"\n (click)=\"signUp()\"\n >\n <ion-icon name=\"enter-outline\" slot=\"start\" />\n @if (signingWith === \"email\") {\n <ion-label>Signing up...</ion-label>\n <ion-spinner\n class=\"ion-margin-start\"\n slot=\"end\"\n name=\"lines-small\"\n />\n } @else {\n <ion-label>Sign up</ion-label>\n }\n </ion-button>\n }\n @case (\"in\") {\n <ion-button\n [disabled]=\"!!signingWith || !validEmail || !password\"\n (click)=\"signIn()\"\n >\n <ion-icon name=\"enter-outline\" slot=\"start\" />\n @if (signingWith === \"email\") {\n <ion-label>Signing in...</ion-label>\n <ion-spinner\n class=\"ion-margin-start\"\n slot=\"end\"\n name=\"lines-small\"\n />\n } @else {\n <ion-label>\n <b>Sign in</b> <ion-text\n color=\"light\"\n style=\"text-transform: none\"\n >with password\n </ion-text>\n </ion-label>\n }\n </ion-button>\n <ion-button disabled=\"disabled\" fill=\"clear\" color=\"medium\">\n or\n </ion-button>\n <ion-button\n size=\"small\"\n [disabled]=\"!validEmail || signingWith\"\n (click)=\"sendSignInLink()\"\n fill=\"outline\"\n color=\"tertiary\"\n >\n <ion-icon name=\"mail-outline\" slot=\"start\" />\n <ion-label>Get sign-in link</ion-label>\n </ion-button>\n }\n }\n </ion-card-content>\n</ion-card>\n"]}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { Component, signal, inject } from '@angular/core';
|
|
2
|
+
import { FormsModule } from '@angular/forms';
|
|
3
|
+
import { ActivatedRoute } from '@angular/router';
|
|
4
|
+
import { Capacitor } from '@capacitor/core';
|
|
5
|
+
import { NavController, IonBackButton, IonButtons, IonCard, IonCardContent, IonCol, IonContent, IonGrid, IonHeader, IonIcon, IonItem, IonItemDivider, IonLabel, IonList, IonRow, IonSpinner, IonText, IonTitle, IonToolbar, } from '@ionic/angular/standalone';
|
|
6
|
+
import { AuthStatuses, LoginEventsHandler, SneatAuthStateService, } from '@sneat/auth-core';
|
|
7
|
+
import { SneatUserService } from '@sneat/auth-core';
|
|
8
|
+
import { AnalyticsService, APP_INFO, } from '@sneat/core';
|
|
9
|
+
import { RandomIdService } from '@sneat/random';
|
|
10
|
+
import { ClassName, SneatBaseComponent } from '@sneat/ui';
|
|
11
|
+
import { Subject, takeUntil } from 'rxjs';
|
|
12
|
+
import { EmailLoginFormComponent, } from './email-login-form/email-login-form.component';
|
|
13
|
+
import { LoginWithTelegramComponent } from './login-with-telegram.component';
|
|
14
|
+
import * as i0 from "@angular/core";
|
|
15
|
+
export class LoginPageComponent extends SneatBaseComponent {
|
|
16
|
+
constructor() {
|
|
17
|
+
super();
|
|
18
|
+
this.analyticsService = inject(AnalyticsService);
|
|
19
|
+
this.route = inject(ActivatedRoute);
|
|
20
|
+
this.navController = inject(NavController);
|
|
21
|
+
this.userService = inject(SneatUserService);
|
|
22
|
+
this.authStateService = inject(SneatAuthStateService);
|
|
23
|
+
this.appInfo = inject(APP_INFO);
|
|
24
|
+
this.loginEventsHandler = inject(LoginEventsHandler, { optional: true });
|
|
25
|
+
this.signingWith = signal(undefined, ...(ngDevMode ? [{ debugName: "signingWith" }] : []));
|
|
26
|
+
this.isNativePlatform = Capacitor.isNativePlatform();
|
|
27
|
+
const appInfo = this.appInfo;
|
|
28
|
+
this.appTitle = appInfo.appTitle || 'Sneat.app';
|
|
29
|
+
if (location.hash.startsWith('#/')) {
|
|
30
|
+
this.redirectTo = location.hash.substring(1);
|
|
31
|
+
}
|
|
32
|
+
this.to = this.route.snapshot.queryParams['to']; // should we subscribe? I believe no.
|
|
33
|
+
const action = location.hash.match(/[#&]action=(\w+)/);
|
|
34
|
+
this.action = action?.[1];
|
|
35
|
+
const userRecordLoaded = new Subject();
|
|
36
|
+
this.userService.userState
|
|
37
|
+
.pipe(takeUntil(userRecordLoaded), this.takeUntilDestroyed())
|
|
38
|
+
.subscribe({
|
|
39
|
+
next: (userState) => {
|
|
40
|
+
if (userState.record) {
|
|
41
|
+
userRecordLoaded.next();
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const redirectTo = this.redirectTo || '/'; // TODO: default one should be app specific.
|
|
47
|
+
this.navController
|
|
48
|
+
.navigateRoot(redirectTo)
|
|
49
|
+
.catch(this.errorLogger.logErrorHandler('Failed to navigate back to ' + redirectTo));
|
|
50
|
+
},
|
|
51
|
+
error: this.errorHandler('Failed to get user state after login'),
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
onEmailFormStatusChanged(signingWith) {
|
|
55
|
+
this.signingWith.set(signingWith);
|
|
56
|
+
}
|
|
57
|
+
async loginWith(provider) {
|
|
58
|
+
this.signingWith.set(provider);
|
|
59
|
+
try {
|
|
60
|
+
await this.authStateService.signInWith(provider);
|
|
61
|
+
// We do not reset this.signingWith in case of succesful sign in as we should redirect from login page
|
|
62
|
+
// and not to allow user to do a double sign-in.
|
|
63
|
+
}
|
|
64
|
+
catch (e) {
|
|
65
|
+
const errMsg = e.errorMessage;
|
|
66
|
+
if (errMsg !== 'The user canceled the sign-in flow.' &&
|
|
67
|
+
!errMsg?.includes('com.apple.AuthenticationServices.AuthorizationError error 1001.')) {
|
|
68
|
+
this.errorLogger.logError(e, `Failed to sign-in with ${provider}`);
|
|
69
|
+
}
|
|
70
|
+
this.signingWith.set(undefined);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
onLoggedIn(userCredential) {
|
|
74
|
+
this.signingWith.set(undefined);
|
|
75
|
+
if (!userCredential.user) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (userCredential.user.email) {
|
|
79
|
+
const prevEmail = localStorage.getItem('emailForSignIn') || '';
|
|
80
|
+
if (!prevEmail) {
|
|
81
|
+
localStorage.setItem('emailForSignIn', userCredential.user.email);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const authState = {
|
|
85
|
+
status: AuthStatuses.authenticated,
|
|
86
|
+
user: userCredential.user,
|
|
87
|
+
};
|
|
88
|
+
this.userService.onUserSignedIn(authState);
|
|
89
|
+
}
|
|
90
|
+
errorHandler(m, eventName, eventParams) {
|
|
91
|
+
return (err) => this.handleError(err, m, eventName, eventParams);
|
|
92
|
+
}
|
|
93
|
+
handleError(err, m, eventName, eventParams) {
|
|
94
|
+
if (eventName) {
|
|
95
|
+
this.analyticsService.logEvent(eventName, eventParams);
|
|
96
|
+
}
|
|
97
|
+
this.errorLogger.logError(err, m, {
|
|
98
|
+
report: !err.code,
|
|
99
|
+
});
|
|
100
|
+
this.signingWith.set(undefined);
|
|
101
|
+
}
|
|
102
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: LoginPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
103
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: LoginPageComponent, isStandalone: true, selector: "sneat-login", providers: [
|
|
104
|
+
{
|
|
105
|
+
provide: ClassName,
|
|
106
|
+
useValue: 'LoginPageComponent',
|
|
107
|
+
},
|
|
108
|
+
RandomIdService,
|
|
109
|
+
], usesInheritance: true, ngImport: i0, template: "<ion-header>\n <ion-toolbar color=\"light\">\n <ion-buttons slot=\"start\">\n <ion-back-button />\n </ion-buttons>\n <ion-title>Login @ {{ appTitle }}</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content id=\"main-content\">\n @if (to) {\n <ion-card color=\"tertiary\">\n <ion-card-content>\n <p>Please sign in to join a team</p>\n </ion-card-content>\n </ion-card>\n }\n\n <p style=\"text-align: center; font-size: smaller\">\n <ion-text color=\"medium\">\n This app is free to use &\n <a target=\"_blank\" href=\"https://github.com/sneat-co\">open source</a>.\n </ion-text>\n </p>\n\n <sneat-email-login-form\n (signingWithChange)=\"onEmailFormStatusChanged()\"\n (loggedIn)=\"onLoggedIn($event)\"\n />\n\n <ion-card>\n <ion-item-divider color=\"light\">\n <ion-label\n color=\"medium\"\n style=\"text-align: center; width: 100%; font-weight: bold\"\n >\n Quick login\n </ion-label>\n </ion-item-divider>\n <ion-grid class=\"ion-grid-layout\">\n @if (!isNativePlatform) {\n <ion-row>\n <ion-col>\n <div class=\"ion-padding\" style=\"text-align: center\">\n <sneat-login-with-telegram />\n </div>\n </ion-col>\n </ion-row>\n }\n <ion-row>\n <ion-col size=\"12\" size-md=\"6\">\n <ion-list lines=\"none\">\n <ion-item\n (click)=\"loginWith('google.com')\"\n tappable\n [disabled]=\"!!signingWith()\"\n >\n @if (signingWith() === \"google.com\") {\n <ion-spinner slot=\"start\" name=\"lines-small\" />\n } @else {\n <ion-icon color=\"danger\" name=\"logo-google\" slot=\"start\" />\n }\n <ion-label color=\"danger\"> Login with Google</ion-label>\n </ion-item>\n </ion-list>\n </ion-col>\n <ion-col size=\"12\" size-md=\"6\">\n <ion-list lines=\"none\">\n <ion-item\n (click)=\"loginWith('apple.com')\"\n tappable\n [disabled]=\"!!signingWith()\"\n >\n @if (signingWith() === \"apple.com\") {\n <ion-spinner slot=\"start\" name=\"lines-small\" />\n } @else {\n <ion-icon color=\"dark\" name=\"logo-apple\" slot=\"start\" />\n }\n\n <ion-label color=\"dark\"> Login with Apple</ion-label>\n </ion-item>\n </ion-list>\n </ion-col>\n </ion-row>\n <ion-row>\n <ion-col size=\"12\" size-md=\"6\">\n <ion-list lines=\"none\">\n <ion-item\n (click)=\"loginWith('microsoft.com')\"\n tappable\n [disabled]=\"!!signingWith()\"\n >\n @if (signingWith() === \"microsoft.com\") {\n <ion-spinner\n slot=\"start\"\n color=\"secondary\"\n name=\"lines-small\"\n />\n } @else {\n <ion-icon slot=\"start\" color=\"secondary\" name=\"logo-windows\" />\n }\n <ion-label color=\"secondary\">Login with Microsoft</ion-label>\n </ion-item>\n </ion-list>\n </ion-col>\n <ion-col size=\"12\" size-md=\"6\">\n <ion-list lines=\"none\">\n <ion-item\n (click)=\"loginWith('facebook.com')\"\n tappable\n [disabled]=\"!!signingWith()\"\n >\n @if (signingWith() === \"facebook.com\") {\n <ion-spinner slot=\"start\" color=\"primary\" name=\"lines-small\" />\n } @else {\n <ion-icon slot=\"start\" color=\"primary\" name=\"logo-facebook\" />\n }\n <ion-label color=\"primary\">Login with Facebook</ion-label>\n </ion-item>\n </ion-list>\n </ion-col>\n </ion-row>\n </ion-grid>\n <!--\t\t\t\t\t\t<ion-item-->\n <!--\t\t\t\t\t\t\t(click)=\"loginWith('github.com')\"-->\n <!--\t\t\t\t\t\t\ttappable-->\n <!--\t\t\t\t\t\t\t[disabled]=\"!!signingWith()\"-->\n <!--\t\t\t\t\t\t>-->\n <!--\t\t\t\t\t\t\t@if (signingWith() === \"github.com\") {-->\n <!--\t\t\t\t\t\t\t\t<ion-spinner slot=\"start\" color=\"tertiary\" name=\"lines-small\" />-->\n <!--\t\t\t\t\t\t\t} @else {-->\n <!--\t\t\t\t\t\t\t\t<ion-icon slot=\"start\" color=\"tertiary\" name=\"logo-github\" />-->\n <!--\t\t\t\t\t\t\t}-->\n <!--\t\t\t\t\t\t\t<ion-label color=\"tertiary\">Login with GitHub</ion-label>-->\n <!--\t\t\t\t\t\t</ion-item>-->\n </ion-card>\n\n <p style=\"text-align: center; font-size: smaller\">\n <ion-text color=\"medium\">\n If any issues get\n <a href=\"mailto:help@sneat.app?subject=Problem+with+login+at+Sneat.app\"\n >help@sneat.app</a\n >\n </ion-text>\n </p>\n</ion-content>\n", dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "component", type: LoginWithTelegramComponent, selector: "sneat-login-with-telegram", inputs: ["isUserAuthenticated", "botID", "size", "requestAccess", "userPic"] }, { kind: "component", type: EmailLoginFormComponent, selector: "sneat-email-login-form", outputs: ["signingWithChange", "loggedIn"] }, { kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonBackButton, selector: "ion-back-button" }, { kind: "component", type: IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: IonCardContent, selector: "ion-card-content", inputs: ["mode"] }, { kind: "component", type: IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "component", type: IonCard, selector: "ion-card", inputs: ["button", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonItemDivider, selector: "ion-item-divider", inputs: ["color", "mode", "sticky"] }, { kind: "component", type: IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: IonRow, selector: "ion-row" }, { kind: "component", type: IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: IonItem, selector: "ion-item", inputs: ["button", "color", "detail", "detailIcon", "disabled", "download", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }, { kind: "component", type: IonGrid, selector: "ion-grid", inputs: ["fixed"] }] }); }
|
|
110
|
+
}
|
|
111
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: LoginPageComponent, decorators: [{
|
|
112
|
+
type: Component,
|
|
113
|
+
args: [{ selector: 'sneat-login', imports: [
|
|
114
|
+
FormsModule,
|
|
115
|
+
LoginWithTelegramComponent,
|
|
116
|
+
EmailLoginFormComponent,
|
|
117
|
+
IonHeader,
|
|
118
|
+
IonToolbar,
|
|
119
|
+
IonButtons,
|
|
120
|
+
IonBackButton,
|
|
121
|
+
IonTitle,
|
|
122
|
+
IonContent,
|
|
123
|
+
IonCardContent,
|
|
124
|
+
IonText,
|
|
125
|
+
IonCard,
|
|
126
|
+
IonItemDivider,
|
|
127
|
+
IonLabel,
|
|
128
|
+
IonRow,
|
|
129
|
+
IonCol,
|
|
130
|
+
IonItem,
|
|
131
|
+
IonSpinner,
|
|
132
|
+
IonIcon,
|
|
133
|
+
IonList,
|
|
134
|
+
IonGrid,
|
|
135
|
+
], providers: [
|
|
136
|
+
{
|
|
137
|
+
provide: ClassName,
|
|
138
|
+
useValue: 'LoginPageComponent',
|
|
139
|
+
},
|
|
140
|
+
RandomIdService,
|
|
141
|
+
], template: "<ion-header>\n <ion-toolbar color=\"light\">\n <ion-buttons slot=\"start\">\n <ion-back-button />\n </ion-buttons>\n <ion-title>Login @ {{ appTitle }}</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content id=\"main-content\">\n @if (to) {\n <ion-card color=\"tertiary\">\n <ion-card-content>\n <p>Please sign in to join a team</p>\n </ion-card-content>\n </ion-card>\n }\n\n <p style=\"text-align: center; font-size: smaller\">\n <ion-text color=\"medium\">\n This app is free to use &\n <a target=\"_blank\" href=\"https://github.com/sneat-co\">open source</a>.\n </ion-text>\n </p>\n\n <sneat-email-login-form\n (signingWithChange)=\"onEmailFormStatusChanged()\"\n (loggedIn)=\"onLoggedIn($event)\"\n />\n\n <ion-card>\n <ion-item-divider color=\"light\">\n <ion-label\n color=\"medium\"\n style=\"text-align: center; width: 100%; font-weight: bold\"\n >\n Quick login\n </ion-label>\n </ion-item-divider>\n <ion-grid class=\"ion-grid-layout\">\n @if (!isNativePlatform) {\n <ion-row>\n <ion-col>\n <div class=\"ion-padding\" style=\"text-align: center\">\n <sneat-login-with-telegram />\n </div>\n </ion-col>\n </ion-row>\n }\n <ion-row>\n <ion-col size=\"12\" size-md=\"6\">\n <ion-list lines=\"none\">\n <ion-item\n (click)=\"loginWith('google.com')\"\n tappable\n [disabled]=\"!!signingWith()\"\n >\n @if (signingWith() === \"google.com\") {\n <ion-spinner slot=\"start\" name=\"lines-small\" />\n } @else {\n <ion-icon color=\"danger\" name=\"logo-google\" slot=\"start\" />\n }\n <ion-label color=\"danger\"> Login with Google</ion-label>\n </ion-item>\n </ion-list>\n </ion-col>\n <ion-col size=\"12\" size-md=\"6\">\n <ion-list lines=\"none\">\n <ion-item\n (click)=\"loginWith('apple.com')\"\n tappable\n [disabled]=\"!!signingWith()\"\n >\n @if (signingWith() === \"apple.com\") {\n <ion-spinner slot=\"start\" name=\"lines-small\" />\n } @else {\n <ion-icon color=\"dark\" name=\"logo-apple\" slot=\"start\" />\n }\n\n <ion-label color=\"dark\"> Login with Apple</ion-label>\n </ion-item>\n </ion-list>\n </ion-col>\n </ion-row>\n <ion-row>\n <ion-col size=\"12\" size-md=\"6\">\n <ion-list lines=\"none\">\n <ion-item\n (click)=\"loginWith('microsoft.com')\"\n tappable\n [disabled]=\"!!signingWith()\"\n >\n @if (signingWith() === \"microsoft.com\") {\n <ion-spinner\n slot=\"start\"\n color=\"secondary\"\n name=\"lines-small\"\n />\n } @else {\n <ion-icon slot=\"start\" color=\"secondary\" name=\"logo-windows\" />\n }\n <ion-label color=\"secondary\">Login with Microsoft</ion-label>\n </ion-item>\n </ion-list>\n </ion-col>\n <ion-col size=\"12\" size-md=\"6\">\n <ion-list lines=\"none\">\n <ion-item\n (click)=\"loginWith('facebook.com')\"\n tappable\n [disabled]=\"!!signingWith()\"\n >\n @if (signingWith() === \"facebook.com\") {\n <ion-spinner slot=\"start\" color=\"primary\" name=\"lines-small\" />\n } @else {\n <ion-icon slot=\"start\" color=\"primary\" name=\"logo-facebook\" />\n }\n <ion-label color=\"primary\">Login with Facebook</ion-label>\n </ion-item>\n </ion-list>\n </ion-col>\n </ion-row>\n </ion-grid>\n <!--\t\t\t\t\t\t<ion-item-->\n <!--\t\t\t\t\t\t\t(click)=\"loginWith('github.com')\"-->\n <!--\t\t\t\t\t\t\ttappable-->\n <!--\t\t\t\t\t\t\t[disabled]=\"!!signingWith()\"-->\n <!--\t\t\t\t\t\t>-->\n <!--\t\t\t\t\t\t\t@if (signingWith() === \"github.com\") {-->\n <!--\t\t\t\t\t\t\t\t<ion-spinner slot=\"start\" color=\"tertiary\" name=\"lines-small\" />-->\n <!--\t\t\t\t\t\t\t} @else {-->\n <!--\t\t\t\t\t\t\t\t<ion-icon slot=\"start\" color=\"tertiary\" name=\"logo-github\" />-->\n <!--\t\t\t\t\t\t\t}-->\n <!--\t\t\t\t\t\t\t<ion-label color=\"tertiary\">Login with GitHub</ion-label>-->\n <!--\t\t\t\t\t\t</ion-item>-->\n </ion-card>\n\n <p style=\"text-align: center; font-size: smaller\">\n <ion-text color=\"medium\">\n If any issues get\n <a href=\"mailto:help@sneat.app?subject=Problem+with+login+at+Sneat.app\"\n >help@sneat.app</a\n >\n </ion-text>\n </p>\n</ion-content>\n" }]
|
|
142
|
+
}], ctorParameters: () => [] });
|
|
143
|
+
//# sourceMappingURL=login-page.component.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login-page.component.js","sourceRoot":"","sources":["../../../../../../../../libs/auth/ui/src/lib/pages/login-page/login-page.component.ts","../../../../../../../../libs/auth/ui/src/lib/pages/login-page/login-page.component.html"],"names":[],"mappings":"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","sourcesContent":["import { Component, signal, inject } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { ActivatedRoute } from '@angular/router';\nimport { Capacitor } from '@capacitor/core';\nimport {\n NavController,\n IonBackButton,\n IonButtons,\n IonCard,\n IonCardContent,\n IonCol,\n IonContent,\n IonGrid,\n IonHeader,\n IonIcon,\n IonItem,\n IonItemDivider,\n IonLabel,\n IonList,\n IonRow,\n IonSpinner,\n IonText,\n IonTitle,\n IonToolbar,\n} from '@ionic/angular/standalone';\nimport {\n AuthProviderID,\n AuthStatuses,\n ILoginEventsHandler,\n ISneatAuthState,\n LoginEventsHandler,\n SneatAuthStateService,\n} from '@sneat/auth-core';\nimport { SneatUserService } from '@sneat/auth-core';\nimport {\n AnalyticsService,\n APP_INFO,\n IAnalyticsService,\n IAppInfo,\n} from '@sneat/core';\nimport { RandomIdService } from '@sneat/random';\nimport { ClassName, SneatBaseComponent } from '@sneat/ui';\nimport { Subject, takeUntil } from 'rxjs';\nimport {\n EmailFormSigningWith,\n EmailLoginFormComponent,\n} from './email-login-form/email-login-form.component';\nimport { UserCredential } from 'firebase/auth';\nimport { LoginWithTelegramComponent } from './login-with-telegram.component';\n\ntype Action = 'join' | 'refuse'; // TODO: inject provider for action descriptions/messages.\n\n@Component({\n selector: 'sneat-login',\n templateUrl: './login-page.component.html',\n imports: [\n FormsModule,\n LoginWithTelegramComponent,\n EmailLoginFormComponent,\n IonHeader,\n IonToolbar,\n IonButtons,\n IonBackButton,\n IonTitle,\n IonContent,\n IonCardContent,\n IonText,\n IonCard,\n IonItemDivider,\n IonLabel,\n IonRow,\n IonCol,\n IonItem,\n IonSpinner,\n IonIcon,\n IonList,\n IonGrid,\n ],\n providers: [\n {\n provide: ClassName,\n useValue: 'LoginPageComponent',\n },\n RandomIdService,\n ],\n})\nexport class LoginPageComponent extends SneatBaseComponent {\n private readonly analyticsService =\n inject<IAnalyticsService>(AnalyticsService);\n private readonly route = inject(ActivatedRoute);\n private readonly navController = inject(NavController);\n private readonly userService = inject(SneatUserService);\n private readonly authStateService = inject(SneatAuthStateService);\n private appInfo = inject<IAppInfo>(APP_INFO);\n private readonly loginEventsHandler = inject<ILoginEventsHandler>(\n LoginEventsHandler,\n { optional: true },\n );\n\n protected readonly signingWith = signal<AuthProviderID | undefined>(\n undefined,\n );\n private readonly redirectTo?: string;\n protected readonly to?: string;\n protected readonly action?: Action; // TODO: document possible values?\n\n protected readonly isNativePlatform = Capacitor.isNativePlatform();\n\n protected readonly appTitle: string;\n\n constructor() {\n super();\n const appInfo = this.appInfo;\n this.appTitle = appInfo.appTitle || 'Sneat.app';\n if (location.hash.startsWith('#/')) {\n this.redirectTo = location.hash.substring(1);\n }\n this.to = this.route.snapshot.queryParams['to']; // should we subscribe? I believe no.\n const action = location.hash.match(/[#&]action=(\\w+)/);\n this.action = action?.[1] as Action;\n\n const userRecordLoaded = new Subject<void>();\n this.userService.userState\n .pipe(takeUntil(userRecordLoaded), this.takeUntilDestroyed())\n .subscribe({\n next: (userState) => {\n if (userState.record) {\n userRecordLoaded.next();\n } else {\n return;\n }\n const redirectTo = this.redirectTo || '/'; // TODO: default one should be app specific.\n this.navController\n .navigateRoot(redirectTo)\n .catch(\n this.errorLogger.logErrorHandler(\n 'Failed to navigate back to ' + redirectTo,\n ),\n );\n },\n error: this.errorHandler('Failed to get user state after login'),\n });\n }\n\n protected onEmailFormStatusChanged(signingWith?: EmailFormSigningWith): void {\n this.signingWith.set(signingWith as AuthProviderID);\n }\n\n protected async loginWith(provider: AuthProviderID) {\n this.signingWith.set(provider);\n try {\n await this.authStateService.signInWith(provider);\n // We do not reset this.signingWith in case of succesful sign in as we should redirect from login page\n // and not to allow user to do a double sign-in.\n } catch (e) {\n const errMsg = (e as { errorMessage?: string }).errorMessage;\n if (\n errMsg !== 'The user canceled the sign-in flow.' &&\n !errMsg?.includes(\n 'com.apple.AuthenticationServices.AuthorizationError error 1001.',\n )\n ) {\n this.errorLogger.logError(e, `Failed to sign-in with ${provider}`);\n }\n this.signingWith.set(undefined);\n }\n }\n\n protected onLoggedIn(userCredential: UserCredential): void {\n this.signingWith.set(undefined);\n if (!userCredential.user) {\n return;\n }\n if (userCredential.user.email) {\n const prevEmail = localStorage.getItem('emailForSignIn') || '';\n if (!prevEmail) {\n localStorage.setItem('emailForSignIn', userCredential.user.email);\n }\n }\n const authState: ISneatAuthState = {\n status: AuthStatuses.authenticated,\n user: userCredential.user,\n };\n this.userService.onUserSignedIn(authState);\n }\n\n private errorHandler(\n m: string,\n eventName?: string,\n eventParams?: Record<string, string>,\n ): (err: unknown) => void {\n return (err) => this.handleError(err, m, eventName, eventParams);\n }\n\n private handleError(\n err: unknown,\n m: string,\n eventName?: string,\n eventParams?: Record<string, string>,\n ): void {\n if (eventName) {\n this.analyticsService.logEvent(eventName, eventParams);\n }\n this.errorLogger.logError(err, m, {\n report: !(err as { code: unknown }).code,\n });\n this.signingWith.set(undefined);\n }\n}\n","<ion-header>\n <ion-toolbar color=\"light\">\n <ion-buttons slot=\"start\">\n <ion-back-button />\n </ion-buttons>\n <ion-title>Login @ {{ appTitle }}</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content id=\"main-content\">\n @if (to) {\n <ion-card color=\"tertiary\">\n <ion-card-content>\n <p>Please sign in to join a team</p>\n </ion-card-content>\n </ion-card>\n }\n\n <p style=\"text-align: center; font-size: smaller\">\n <ion-text color=\"medium\">\n This app is free to use &\n <a target=\"_blank\" href=\"https://github.com/sneat-co\">open source</a>.\n </ion-text>\n </p>\n\n <sneat-email-login-form\n (signingWithChange)=\"onEmailFormStatusChanged()\"\n (loggedIn)=\"onLoggedIn($event)\"\n />\n\n <ion-card>\n <ion-item-divider color=\"light\">\n <ion-label\n color=\"medium\"\n style=\"text-align: center; width: 100%; font-weight: bold\"\n >\n Quick login\n </ion-label>\n </ion-item-divider>\n <ion-grid class=\"ion-grid-layout\">\n @if (!isNativePlatform) {\n <ion-row>\n <ion-col>\n <div class=\"ion-padding\" style=\"text-align: center\">\n <sneat-login-with-telegram />\n </div>\n </ion-col>\n </ion-row>\n }\n <ion-row>\n <ion-col size=\"12\" size-md=\"6\">\n <ion-list lines=\"none\">\n <ion-item\n (click)=\"loginWith('google.com')\"\n tappable\n [disabled]=\"!!signingWith()\"\n >\n @if (signingWith() === \"google.com\") {\n <ion-spinner slot=\"start\" name=\"lines-small\" />\n } @else {\n <ion-icon color=\"danger\" name=\"logo-google\" slot=\"start\" />\n }\n <ion-label color=\"danger\"> Login with Google</ion-label>\n </ion-item>\n </ion-list>\n </ion-col>\n <ion-col size=\"12\" size-md=\"6\">\n <ion-list lines=\"none\">\n <ion-item\n (click)=\"loginWith('apple.com')\"\n tappable\n [disabled]=\"!!signingWith()\"\n >\n @if (signingWith() === \"apple.com\") {\n <ion-spinner slot=\"start\" name=\"lines-small\" />\n } @else {\n <ion-icon color=\"dark\" name=\"logo-apple\" slot=\"start\" />\n }\n\n <ion-label color=\"dark\"> Login with Apple</ion-label>\n </ion-item>\n </ion-list>\n </ion-col>\n </ion-row>\n <ion-row>\n <ion-col size=\"12\" size-md=\"6\">\n <ion-list lines=\"none\">\n <ion-item\n (click)=\"loginWith('microsoft.com')\"\n tappable\n [disabled]=\"!!signingWith()\"\n >\n @if (signingWith() === \"microsoft.com\") {\n <ion-spinner\n slot=\"start\"\n color=\"secondary\"\n name=\"lines-small\"\n />\n } @else {\n <ion-icon slot=\"start\" color=\"secondary\" name=\"logo-windows\" />\n }\n <ion-label color=\"secondary\">Login with Microsoft</ion-label>\n </ion-item>\n </ion-list>\n </ion-col>\n <ion-col size=\"12\" size-md=\"6\">\n <ion-list lines=\"none\">\n <ion-item\n (click)=\"loginWith('facebook.com')\"\n tappable\n [disabled]=\"!!signingWith()\"\n >\n @if (signingWith() === \"facebook.com\") {\n <ion-spinner slot=\"start\" color=\"primary\" name=\"lines-small\" />\n } @else {\n <ion-icon slot=\"start\" color=\"primary\" name=\"logo-facebook\" />\n }\n <ion-label color=\"primary\">Login with Facebook</ion-label>\n </ion-item>\n </ion-list>\n </ion-col>\n </ion-row>\n </ion-grid>\n <!--\t\t\t\t\t\t<ion-item-->\n <!--\t\t\t\t\t\t\t(click)=\"loginWith('github.com')\"-->\n <!--\t\t\t\t\t\t\ttappable-->\n <!--\t\t\t\t\t\t\t[disabled]=\"!!signingWith()\"-->\n <!--\t\t\t\t\t\t>-->\n <!--\t\t\t\t\t\t\t@if (signingWith() === \"github.com\") {-->\n <!--\t\t\t\t\t\t\t\t<ion-spinner slot=\"start\" color=\"tertiary\" name=\"lines-small\" />-->\n <!--\t\t\t\t\t\t\t} @else {-->\n <!--\t\t\t\t\t\t\t\t<ion-icon slot=\"start\" color=\"tertiary\" name=\"logo-github\" />-->\n <!--\t\t\t\t\t\t\t}-->\n <!--\t\t\t\t\t\t\t<ion-label color=\"tertiary\">Login with GitHub</ion-label>-->\n <!--\t\t\t\t\t\t</ion-item>-->\n </ion-card>\n\n <p style=\"text-align: center; font-size: smaller\">\n <ion-text color=\"medium\">\n If any issues get\n <a href=\"mailto:help@sneat.app?subject=Problem+with+login+at+Sneat.app\"\n >help@sneat.app</a\n >\n </ion-text>\n </p>\n</ion-content>\n"]}
|