@sneat/auth-ui 0.1.0
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/eslint.config.cjs +7 -0
- package/ng-package.json +7 -0
- package/package.json +19 -0
- package/project.json +38 -0
- package/src/index.ts +3 -0
- package/src/lib/components/auth-menu-item/auth-menu-item.component.html +59 -0
- package/src/lib/components/auth-menu-item/auth-menu-item.component.spec.ts +5 -0
- package/src/lib/components/auth-menu-item/auth-menu-item.component.ts +116 -0
- package/src/lib/components/index.ts +3 -0
- package/src/lib/components/user-auth-providers/user-auth-accounts.component.html +127 -0
- package/src/lib/components/user-auth-providers/user-auth-accounts.component.spec.ts +47 -0
- package/src/lib/components/user-auth-providers/user-auth-accounts.component.ts +109 -0
- package/src/lib/components/user-auth-providers/user-auth-provider-status.html +42 -0
- package/src/lib/components/user-auth-providers/user-auth-provider-status.ts +172 -0
- package/src/lib/components/user-required-fields/index.ts +2 -0
- package/src/lib/components/user-required-fields/user-required-fields-modal.component.html +64 -0
- package/src/lib/components/user-required-fields/user-required-fields-modal.component.spec.ts +45 -0
- package/src/lib/components/user-required-fields/user-required-fields-modal.component.ts +141 -0
- package/src/lib/components/user-required-fields/user-required-fields.service.spec.ts +335 -0
- package/src/lib/components/user-required-fields/user-required-fields.service.ts +23 -0
- package/src/lib/pages/login-page/email-login-form/email-login-form.component.html +165 -0
- package/src/lib/pages/login-page/email-login-form/email-login-form.component.spec.ts +59 -0
- package/src/lib/pages/login-page/email-login-form/email-login-form.component.ts +356 -0
- package/src/lib/pages/login-page/index.ts +3 -0
- package/src/lib/pages/login-page/login-page.component.html +146 -0
- package/src/lib/pages/login-page/login-page.component.spec.ts +5 -0
- package/src/lib/pages/login-page/login-page.component.ts +209 -0
- package/src/lib/pages/login-page/login-with-telegram.component.spec.ts +42 -0
- package/src/lib/pages/login-page/login-with-telegram.component.ts +82 -0
- package/src/lib/pages/login-page/sneat-auth-with-telegram.service.spec.ts +31 -0
- package/src/lib/pages/login-page/sneat-auth-with-telegram.service.ts +56 -0
- package/src/lib/pages/sign-in-from-email-link/sign-in-from-email-link-page.component.html +35 -0
- package/src/lib/pages/sign-in-from-email-link/sign-in-from-email-link-page.component.spec.ts +43 -0
- package/src/lib/pages/sign-in-from-email-link/sign-in-from-email-link-page.component.ts +70 -0
- package/src/lib/pages/sneat-auth-routing.module.ts +25 -0
- package/src/lib/pipes/person-names.pipe.spec.ts +317 -0
- package/src/lib/pipes/person-names.pipe.ts +31 -0
- package/src/test-setup.ts +3 -0
- package/tsconfig.json +13 -0
- package/tsconfig.lib.json +19 -0
- package/tsconfig.lib.prod.json +7 -0
- package/tsconfig.spec.json +31 -0
- package/vite.config.mts +10 -0
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Component,
|
|
3
|
+
EventEmitter,
|
|
4
|
+
Output,
|
|
5
|
+
ViewChild,
|
|
6
|
+
inject,
|
|
7
|
+
} from '@angular/core';
|
|
8
|
+
// import { getApp } from '@angular/fire/app';
|
|
9
|
+
// import { getAuth } from '@angular/fire/auth';
|
|
10
|
+
import {
|
|
11
|
+
Auth,
|
|
12
|
+
sendPasswordResetEmail,
|
|
13
|
+
sendSignInLinkToEmail,
|
|
14
|
+
} from '@angular/fire/auth';
|
|
15
|
+
import { FormsModule } from '@angular/forms';
|
|
16
|
+
import {
|
|
17
|
+
ToastController,
|
|
18
|
+
IonButton,
|
|
19
|
+
IonButtons,
|
|
20
|
+
IonCard,
|
|
21
|
+
IonCardContent,
|
|
22
|
+
IonIcon,
|
|
23
|
+
IonInput,
|
|
24
|
+
IonItem,
|
|
25
|
+
IonItemDivider,
|
|
26
|
+
IonLabel,
|
|
27
|
+
IonSegment,
|
|
28
|
+
IonSegmentButton,
|
|
29
|
+
IonSpinner,
|
|
30
|
+
IonText,
|
|
31
|
+
} from '@ionic/angular/standalone';
|
|
32
|
+
import { SneatApiService } from '@sneat/api';
|
|
33
|
+
import { IInitUserRecordRequest, UserRecordService } from '@sneat/auth-core';
|
|
34
|
+
import { createSetFocusToInput } from '@sneat/ui';
|
|
35
|
+
import {
|
|
36
|
+
AnalyticsService,
|
|
37
|
+
APP_INFO,
|
|
38
|
+
IAnalyticsService,
|
|
39
|
+
IAppInfo,
|
|
40
|
+
} from '@sneat/core';
|
|
41
|
+
import { ErrorLogger, IErrorLogger } from '@sneat/core';
|
|
42
|
+
import { RandomIdService } from '@sneat/random';
|
|
43
|
+
import { UserCredential, sendEmailVerification } from 'firebase/auth';
|
|
44
|
+
import { FirebaseError } from 'firebase/app';
|
|
45
|
+
|
|
46
|
+
export type EmailFormSigningWith = 'email' | 'emailLink' | 'resetPassword';
|
|
47
|
+
import {
|
|
48
|
+
createUserWithEmailAndPassword,
|
|
49
|
+
signInWithEmailAndPassword,
|
|
50
|
+
} from 'firebase/auth';
|
|
51
|
+
|
|
52
|
+
@Component({
|
|
53
|
+
selector: 'sneat-email-login-form',
|
|
54
|
+
templateUrl: 'email-login-form.component.html',
|
|
55
|
+
imports: [
|
|
56
|
+
FormsModule,
|
|
57
|
+
IonCard,
|
|
58
|
+
IonItemDivider,
|
|
59
|
+
IonSegment,
|
|
60
|
+
IonSegmentButton,
|
|
61
|
+
IonIcon,
|
|
62
|
+
IonLabel,
|
|
63
|
+
IonCardContent,
|
|
64
|
+
IonItem,
|
|
65
|
+
IonInput,
|
|
66
|
+
IonSpinner,
|
|
67
|
+
IonText,
|
|
68
|
+
IonButton,
|
|
69
|
+
IonButtons,
|
|
70
|
+
],
|
|
71
|
+
})
|
|
72
|
+
export class EmailLoginFormComponent {
|
|
73
|
+
readonly appInfo = inject<IAppInfo>(APP_INFO);
|
|
74
|
+
private readonly analyticsService =
|
|
75
|
+
inject<IAnalyticsService>(AnalyticsService);
|
|
76
|
+
private readonly errorLogger = inject<IErrorLogger>(ErrorLogger);
|
|
77
|
+
private readonly toastController = inject(ToastController);
|
|
78
|
+
private readonly afAuth = inject(Auth);
|
|
79
|
+
private readonly randomIdService = inject(RandomIdService);
|
|
80
|
+
private readonly sneatApiService = inject(SneatApiService);
|
|
81
|
+
private readonly userRecordService = inject(UserRecordService);
|
|
82
|
+
|
|
83
|
+
protected sign: 'in' | 'up' = 'up'; // TODO: document here what 'in' & 'up' means
|
|
84
|
+
protected email = '';
|
|
85
|
+
protected password = '';
|
|
86
|
+
protected firstName = '';
|
|
87
|
+
protected lastName = '';
|
|
88
|
+
protected spaceTitle = '';
|
|
89
|
+
protected wrongPassword = false;
|
|
90
|
+
|
|
91
|
+
protected signingWith?: EmailFormSigningWith;
|
|
92
|
+
|
|
93
|
+
@Output() readonly signingWithChange = new EventEmitter<
|
|
94
|
+
EmailFormSigningWith | undefined
|
|
95
|
+
>();
|
|
96
|
+
@Output() readonly loggedIn = new EventEmitter<UserCredential>();
|
|
97
|
+
|
|
98
|
+
@ViewChild('emailInput', { static: true }) emailInput?: IonInput;
|
|
99
|
+
@ViewChild('spaceTitleInput', { static: false }) spaceTitleInput?: IonInput;
|
|
100
|
+
|
|
101
|
+
public readonly setFocusToInput = createSetFocusToInput(this.errorLogger);
|
|
102
|
+
|
|
103
|
+
constructor() {
|
|
104
|
+
this.email = localStorage.getItem('emailForSignIn') || '';
|
|
105
|
+
if (this.email) {
|
|
106
|
+
this.sign = 'in';
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
public get validEmail(): boolean {
|
|
111
|
+
const email = this.email,
|
|
112
|
+
i = email?.indexOf('@');
|
|
113
|
+
return i > 0 && i < email.length - 1;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
public getFirebaseAuth(): Auth {
|
|
117
|
+
return this.afAuth as unknown as Auth; // TODO: pending https://github.com/angular/angularfire/pull/3402
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
public async signUp(): Promise<void> {
|
|
121
|
+
if (!this.firstName) {
|
|
122
|
+
// this.toaster.showToast('Full name is required');
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
// this.signingWith = 'email';
|
|
126
|
+
this.email = this.email.trim();
|
|
127
|
+
const email = this.email;
|
|
128
|
+
const firstName = this.firstName.trim();
|
|
129
|
+
const lastName = this.lastName.trim();
|
|
130
|
+
if (!email) {
|
|
131
|
+
alert('Email is a required field');
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (!firstName) {
|
|
135
|
+
alert('First name is a required field');
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (!lastName) {
|
|
139
|
+
alert('Last name is a required field');
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
this.spaceTitle = this.spaceTitle.trim();
|
|
143
|
+
const spaceTitle = this.spaceTitle;
|
|
144
|
+
if (this.appInfo.requiredSpaceType && !spaceTitle) {
|
|
145
|
+
alert('Company title is a required field');
|
|
146
|
+
this.setFocusToSpaceTitle();
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
localStorage.setItem('emailForSignIn', email);
|
|
150
|
+
const password = this.randomIdService.newRandomId({ len: 9 });
|
|
151
|
+
|
|
152
|
+
this.setSigningWith('email');
|
|
153
|
+
try {
|
|
154
|
+
// const auth = getAuth(getApp());
|
|
155
|
+
const auth = this.getFirebaseAuth();
|
|
156
|
+
const userCredential = await createUserWithEmailAndPassword(
|
|
157
|
+
auth,
|
|
158
|
+
email,
|
|
159
|
+
password,
|
|
160
|
+
);
|
|
161
|
+
this.sendVerificationEmail(userCredential);
|
|
162
|
+
userCredential.user
|
|
163
|
+
?.getIdToken()
|
|
164
|
+
.then((token) => {
|
|
165
|
+
this.sneatApiService.setApiAuthToken(token);
|
|
166
|
+
const request: IInitUserRecordRequest = {
|
|
167
|
+
authProvider: 'password',
|
|
168
|
+
email,
|
|
169
|
+
ianaTimezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
170
|
+
names: { firstName, lastName },
|
|
171
|
+
space: this.appInfo.requiredSpaceType
|
|
172
|
+
? {
|
|
173
|
+
type: this.appInfo.requiredSpaceType,
|
|
174
|
+
title: spaceTitle,
|
|
175
|
+
}
|
|
176
|
+
: undefined,
|
|
177
|
+
};
|
|
178
|
+
this.userRecordService.initUserRecord(request).subscribe({
|
|
179
|
+
next: () => this.onLoggedIn(userCredential),
|
|
180
|
+
error: (err) => {
|
|
181
|
+
this.analyticsService.logEvent('FailedToSetUserTitle');
|
|
182
|
+
this.errorLogger.logError(err, 'Failed to set user title', {
|
|
183
|
+
feedback: false,
|
|
184
|
+
});
|
|
185
|
+
this.onLoggedIn(userCredential);
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
})
|
|
189
|
+
.catch(
|
|
190
|
+
this.errorHandler(
|
|
191
|
+
'Failed to get Firebase ID token',
|
|
192
|
+
'FirebaseGetIdTokenFailed',
|
|
193
|
+
),
|
|
194
|
+
);
|
|
195
|
+
} catch (e) {
|
|
196
|
+
this.handleError(
|
|
197
|
+
e,
|
|
198
|
+
'Failed to sign up with email',
|
|
199
|
+
'FailedToSignUpWithEmail',
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
public keyupEnter(): void {
|
|
205
|
+
switch (this.sign) {
|
|
206
|
+
case 'in':
|
|
207
|
+
this.signIn();
|
|
208
|
+
break;
|
|
209
|
+
case 'up':
|
|
210
|
+
this.signUp().catch(() => this.errorHandler('Failed to sign up'));
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
public signIn(): void {
|
|
216
|
+
this.setSigningWith('email');
|
|
217
|
+
this.email = this.email.trim();
|
|
218
|
+
this.wrongPassword = false;
|
|
219
|
+
this.saveEmailForReuse();
|
|
220
|
+
// const auth = getAuth(getApp());
|
|
221
|
+
const auth = this.getFirebaseAuth();
|
|
222
|
+
signInWithEmailAndPassword(auth, this.email, this.password)
|
|
223
|
+
.then((userCredential) => {
|
|
224
|
+
this.onLoggedIn(userCredential); // TODO: add analytics event
|
|
225
|
+
})
|
|
226
|
+
.catch(
|
|
227
|
+
this.errorHandler('Failed to sign in with email & password', 'email'),
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
private saveEmailForReuse(): void {
|
|
232
|
+
localStorage.setItem('emailForSignIn', this.email);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
public sendSignInLink(): void {
|
|
236
|
+
this.setSigningWith('emailLink');
|
|
237
|
+
this.email = this.email.trim();
|
|
238
|
+
this.saveEmailForReuse();
|
|
239
|
+
sendSignInLinkToEmail(this.afAuth, this.email, {
|
|
240
|
+
// url: 'https://dailyscrum.app/pwa/sign-in',
|
|
241
|
+
url: document.baseURI + 'sign-in-from-email-link',
|
|
242
|
+
handleCodeInApp: true,
|
|
243
|
+
})
|
|
244
|
+
.then(() => {
|
|
245
|
+
this.showToast(`Sign-in link has been sent to email: ${this.email}`);
|
|
246
|
+
this.setSigningWith(undefined);
|
|
247
|
+
})
|
|
248
|
+
.catch(
|
|
249
|
+
this.errorHandler(
|
|
250
|
+
'Failed to send sign in link to email',
|
|
251
|
+
'FailedToSendSignInLinkToEmail',
|
|
252
|
+
),
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
private showToast(message: string): void {
|
|
257
|
+
this.toastController
|
|
258
|
+
.create({
|
|
259
|
+
message,
|
|
260
|
+
position: 'middle',
|
|
261
|
+
keyboardClose: true,
|
|
262
|
+
duration: 3000,
|
|
263
|
+
color: 'tertiary',
|
|
264
|
+
icon: 'send-outline',
|
|
265
|
+
buttons: [{ icon: 'close', role: 'cancel' }],
|
|
266
|
+
})
|
|
267
|
+
.then((toast) => {
|
|
268
|
+
toast
|
|
269
|
+
.present()
|
|
270
|
+
.catch(
|
|
271
|
+
this.errorLogger.logErrorHandler(
|
|
272
|
+
'Failed to present toast about password reset email sent success',
|
|
273
|
+
),
|
|
274
|
+
);
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
public resetPassword(): void {
|
|
279
|
+
this.setSigningWith('resetPassword');
|
|
280
|
+
sendPasswordResetEmail(this.afAuth, this.email)
|
|
281
|
+
.then(() => {
|
|
282
|
+
this.setSigningWith(undefined);
|
|
283
|
+
this.showToast(
|
|
284
|
+
`Password reset link has been sent to email: ${this.email}`,
|
|
285
|
+
);
|
|
286
|
+
})
|
|
287
|
+
.catch(
|
|
288
|
+
this.errorHandler(
|
|
289
|
+
'Failed to send password reset email',
|
|
290
|
+
'FailedToSendPasswordResetEmail',
|
|
291
|
+
),
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
private sendVerificationEmail(userCredential: UserCredential): void {
|
|
296
|
+
setTimeout(async () => {
|
|
297
|
+
try {
|
|
298
|
+
await sendEmailVerification(userCredential.user);
|
|
299
|
+
} catch (e) {
|
|
300
|
+
this.handleError(e, 'Failed to send verification email');
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
private onLoggedIn(userCredential: UserCredential): void {
|
|
306
|
+
this.loggedIn.emit(userCredential);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
private errorHandler(
|
|
310
|
+
m: string,
|
|
311
|
+
eventName?: string,
|
|
312
|
+
eventParams?: Record<string, string>,
|
|
313
|
+
): (err: unknown) => void {
|
|
314
|
+
return (err) => this.handleError(err, m, eventName, eventParams);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
private handleError(
|
|
318
|
+
err: unknown,
|
|
319
|
+
m: string,
|
|
320
|
+
eventName?: string,
|
|
321
|
+
eventParams?: Record<string, string>,
|
|
322
|
+
): void {
|
|
323
|
+
this.setSigningWith(undefined);
|
|
324
|
+
if (eventName) {
|
|
325
|
+
this.analyticsService.logEvent(eventName, eventParams);
|
|
326
|
+
}
|
|
327
|
+
if ((err as FirebaseError).code === 'auth/wrong-password') {
|
|
328
|
+
this.wrongPassword = true;
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
this.errorLogger.logError(err, m, {
|
|
332
|
+
report: !(err as { code: unknown }).code,
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
private setSigningWith(signingWith?: EmailFormSigningWith): void {
|
|
337
|
+
this.signingWith = signingWith;
|
|
338
|
+
this.signingWithChange.emit(signingWith);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
signChanged(): void {
|
|
342
|
+
this.setFocusToEmail();
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
ionViewDidEnter(): void {
|
|
346
|
+
this.setFocusToEmail();
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
private setFocusToEmail(): void {
|
|
350
|
+
this.setFocusToInput(undefined /*this.emailInput*/);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
private setFocusToSpaceTitle(): void {
|
|
354
|
+
this.setFocusToInput(undefined /*this.spaceTitleInput*/);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
<ion-header>
|
|
2
|
+
<ion-toolbar color="light">
|
|
3
|
+
<ion-buttons slot="start">
|
|
4
|
+
<ion-back-button />
|
|
5
|
+
</ion-buttons>
|
|
6
|
+
<ion-title>Login @ {{ appTitle }}</ion-title>
|
|
7
|
+
</ion-toolbar>
|
|
8
|
+
</ion-header>
|
|
9
|
+
|
|
10
|
+
<ion-content id="main-content">
|
|
11
|
+
@if (to) {
|
|
12
|
+
<ion-card color="tertiary">
|
|
13
|
+
<ion-card-content>
|
|
14
|
+
<p>Please sign in to join a team</p>
|
|
15
|
+
</ion-card-content>
|
|
16
|
+
</ion-card>
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
<p style="text-align: center; font-size: smaller">
|
|
20
|
+
<ion-text color="medium">
|
|
21
|
+
This app is free to use &
|
|
22
|
+
<a target="_blank" href="https://github.com/sneat-co">open source</a>.
|
|
23
|
+
</ion-text>
|
|
24
|
+
</p>
|
|
25
|
+
|
|
26
|
+
<sneat-email-login-form
|
|
27
|
+
(signingWithChange)="onEmailFormStatusChanged()"
|
|
28
|
+
(loggedIn)="onLoggedIn($event)"
|
|
29
|
+
/>
|
|
30
|
+
|
|
31
|
+
<ion-card>
|
|
32
|
+
<ion-item-divider color="light">
|
|
33
|
+
<ion-label
|
|
34
|
+
color="medium"
|
|
35
|
+
style="text-align: center; width: 100%; font-weight: bold"
|
|
36
|
+
>
|
|
37
|
+
Quick login
|
|
38
|
+
</ion-label>
|
|
39
|
+
</ion-item-divider>
|
|
40
|
+
<ion-grid class="ion-grid-layout">
|
|
41
|
+
@if (!isNativePlatform) {
|
|
42
|
+
<ion-row>
|
|
43
|
+
<ion-col>
|
|
44
|
+
<div class="ion-padding" style="text-align: center">
|
|
45
|
+
<sneat-login-with-telegram />
|
|
46
|
+
</div>
|
|
47
|
+
</ion-col>
|
|
48
|
+
</ion-row>
|
|
49
|
+
}
|
|
50
|
+
<ion-row>
|
|
51
|
+
<ion-col size="12" size-md="6">
|
|
52
|
+
<ion-list lines="none">
|
|
53
|
+
<ion-item
|
|
54
|
+
(click)="loginWith('google.com')"
|
|
55
|
+
tappable
|
|
56
|
+
[disabled]="!!signingWith()"
|
|
57
|
+
>
|
|
58
|
+
@if (signingWith() === "google.com") {
|
|
59
|
+
<ion-spinner slot="start" name="lines-small" />
|
|
60
|
+
} @else {
|
|
61
|
+
<ion-icon color="danger" name="logo-google" slot="start" />
|
|
62
|
+
}
|
|
63
|
+
<ion-label color="danger"> Login with Google</ion-label>
|
|
64
|
+
</ion-item>
|
|
65
|
+
</ion-list>
|
|
66
|
+
</ion-col>
|
|
67
|
+
<ion-col size="12" size-md="6">
|
|
68
|
+
<ion-list lines="none">
|
|
69
|
+
<ion-item
|
|
70
|
+
(click)="loginWith('apple.com')"
|
|
71
|
+
tappable
|
|
72
|
+
[disabled]="!!signingWith()"
|
|
73
|
+
>
|
|
74
|
+
@if (signingWith() === "apple.com") {
|
|
75
|
+
<ion-spinner slot="start" name="lines-small" />
|
|
76
|
+
} @else {
|
|
77
|
+
<ion-icon color="dark" name="logo-apple" slot="start" />
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
<ion-label color="dark"> Login with Apple</ion-label>
|
|
81
|
+
</ion-item>
|
|
82
|
+
</ion-list>
|
|
83
|
+
</ion-col>
|
|
84
|
+
</ion-row>
|
|
85
|
+
<ion-row>
|
|
86
|
+
<ion-col size="12" size-md="6">
|
|
87
|
+
<ion-list lines="none">
|
|
88
|
+
<ion-item
|
|
89
|
+
(click)="loginWith('microsoft.com')"
|
|
90
|
+
tappable
|
|
91
|
+
[disabled]="!!signingWith()"
|
|
92
|
+
>
|
|
93
|
+
@if (signingWith() === "microsoft.com") {
|
|
94
|
+
<ion-spinner
|
|
95
|
+
slot="start"
|
|
96
|
+
color="secondary"
|
|
97
|
+
name="lines-small"
|
|
98
|
+
/>
|
|
99
|
+
} @else {
|
|
100
|
+
<ion-icon slot="start" color="secondary" name="logo-windows" />
|
|
101
|
+
}
|
|
102
|
+
<ion-label color="secondary">Login with Microsoft</ion-label>
|
|
103
|
+
</ion-item>
|
|
104
|
+
</ion-list>
|
|
105
|
+
</ion-col>
|
|
106
|
+
<ion-col size="12" size-md="6">
|
|
107
|
+
<ion-list lines="none">
|
|
108
|
+
<ion-item
|
|
109
|
+
(click)="loginWith('facebook.com')"
|
|
110
|
+
tappable
|
|
111
|
+
[disabled]="!!signingWith()"
|
|
112
|
+
>
|
|
113
|
+
@if (signingWith() === "facebook.com") {
|
|
114
|
+
<ion-spinner slot="start" color="primary" name="lines-small" />
|
|
115
|
+
} @else {
|
|
116
|
+
<ion-icon slot="start" color="primary" name="logo-facebook" />
|
|
117
|
+
}
|
|
118
|
+
<ion-label color="primary">Login with Facebook</ion-label>
|
|
119
|
+
</ion-item>
|
|
120
|
+
</ion-list>
|
|
121
|
+
</ion-col>
|
|
122
|
+
</ion-row>
|
|
123
|
+
</ion-grid>
|
|
124
|
+
<!-- <ion-item-->
|
|
125
|
+
<!-- (click)="loginWith('github.com')"-->
|
|
126
|
+
<!-- tappable-->
|
|
127
|
+
<!-- [disabled]="!!signingWith()"-->
|
|
128
|
+
<!-- >-->
|
|
129
|
+
<!-- @if (signingWith() === "github.com") {-->
|
|
130
|
+
<!-- <ion-spinner slot="start" color="tertiary" name="lines-small" />-->
|
|
131
|
+
<!-- } @else {-->
|
|
132
|
+
<!-- <ion-icon slot="start" color="tertiary" name="logo-github" />-->
|
|
133
|
+
<!-- }-->
|
|
134
|
+
<!-- <ion-label color="tertiary">Login with GitHub</ion-label>-->
|
|
135
|
+
<!-- </ion-item>-->
|
|
136
|
+
</ion-card>
|
|
137
|
+
|
|
138
|
+
<p style="text-align: center; font-size: smaller">
|
|
139
|
+
<ion-text color="medium">
|
|
140
|
+
If any issues get
|
|
141
|
+
<a href="mailto:help@sneat.app?subject=Problem+with+login+at+Sneat.app"
|
|
142
|
+
>help@sneat.app</a
|
|
143
|
+
>
|
|
144
|
+
</ion-text>
|
|
145
|
+
</p>
|
|
146
|
+
</ion-content>
|