@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
package/ng-package.json
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sneat/auth-ui",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
6
|
+
},
|
|
7
|
+
"peerDependencies": {
|
|
8
|
+
"@angular/common": ">=21.0.0",
|
|
9
|
+
"@angular/core": ">=21.0.0",
|
|
10
|
+
"@ionic/angular": ">=8",
|
|
11
|
+
"firebase": "*",
|
|
12
|
+
"@angular/fire": ">=20",
|
|
13
|
+
"@sneat/random": "*"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"tslib": "2.8.1"
|
|
17
|
+
},
|
|
18
|
+
"type": "module"
|
|
19
|
+
}
|
package/project.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "auth-ui",
|
|
3
|
+
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
|
|
4
|
+
"projectType": "library",
|
|
5
|
+
"sourceRoot": "libs/auth/ui/src",
|
|
6
|
+
"prefix": "sneat",
|
|
7
|
+
"targets": {
|
|
8
|
+
"build": {
|
|
9
|
+
"executor": "@nx/angular:ng-packagr-lite",
|
|
10
|
+
"outputs": [
|
|
11
|
+
"{workspaceRoot}/dist/libs/auth/ui"
|
|
12
|
+
],
|
|
13
|
+
"options": {
|
|
14
|
+
"project": "libs/auth/ui/ng-package.json",
|
|
15
|
+
"tsConfig": "libs/auth/ui/tsconfig.lib.json"
|
|
16
|
+
},
|
|
17
|
+
"configurations": {
|
|
18
|
+
"production": {
|
|
19
|
+
"tsConfig": "libs/auth/ui/tsconfig.lib.prod.json"
|
|
20
|
+
},
|
|
21
|
+
"development": {}
|
|
22
|
+
},
|
|
23
|
+
"defaultConfiguration": "production"
|
|
24
|
+
},
|
|
25
|
+
"test": {
|
|
26
|
+
"executor": "@nx/vitest:test",
|
|
27
|
+
"outputs": [
|
|
28
|
+
"{workspaceRoot}/coverage/libs/auth/ui"
|
|
29
|
+
],
|
|
30
|
+
"options": {
|
|
31
|
+
"tsConfig": "libs/auth/ui/tsconfig.spec.json"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"lint": {
|
|
35
|
+
"executor": "@nx/eslint:lint"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<ion-item-divider color="light">
|
|
2
|
+
@if ($authStatus() === "authenticated") {
|
|
3
|
+
<ion-label>Signed in as</ion-label>
|
|
4
|
+
} @else {
|
|
5
|
+
<ion-label>Authentication</ion-label>
|
|
6
|
+
}
|
|
7
|
+
</ion-item-divider>
|
|
8
|
+
@if (!$authState()) {
|
|
9
|
+
<ion-item>
|
|
10
|
+
<ion-label>Loading....</ion-label>
|
|
11
|
+
</ion-item>
|
|
12
|
+
} @else if ($isAuthenticating()) {
|
|
13
|
+
<ion-item routerLink="login">
|
|
14
|
+
<ion-label>Authenticating....</ion-label>
|
|
15
|
+
</ion-item>
|
|
16
|
+
}
|
|
17
|
+
@if ($authStatus() === "notAuthenticated") {
|
|
18
|
+
<ion-item routerLink="/login" tappable="true">
|
|
19
|
+
<ion-icon slot="start" name="person-circle-outline" />
|
|
20
|
+
<ion-label color="primary"> Please sign in</ion-label>
|
|
21
|
+
</ion-item>
|
|
22
|
+
}
|
|
23
|
+
@if ($authStatus() === "authenticated") {
|
|
24
|
+
<ion-item
|
|
25
|
+
tappable="true"
|
|
26
|
+
routerLink="/my"
|
|
27
|
+
routerDirection="forward"
|
|
28
|
+
(click)="closeMenu()"
|
|
29
|
+
>
|
|
30
|
+
<!-- <ion-buttons slot="start" class="ion-no-margin">-->
|
|
31
|
+
<!-- <ion-button disabled color="medium" >-->
|
|
32
|
+
<!-- <ion-icon slot="start" name="person-circle-outline"></ion-icon>-->
|
|
33
|
+
<!-- </ion-button>-->
|
|
34
|
+
<!-- </ion-buttons>-->
|
|
35
|
+
<ion-icon slot="start" name="person-circle-outline" />
|
|
36
|
+
|
|
37
|
+
<ion-label color="medium">
|
|
38
|
+
@if ($user()?.record?.names; as names) {
|
|
39
|
+
{{ names | personNames }}
|
|
40
|
+
} @else {
|
|
41
|
+
@if ($authState()?.user; as user) {
|
|
42
|
+
{{ user.displayName || user.email || user.phoneNumber || user.uid }}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
</ion-label>
|
|
46
|
+
|
|
47
|
+
<ion-buttons slot="end">
|
|
48
|
+
<!-- <ion-button color="medium" title="Settings">-->
|
|
49
|
+
<!-- <ion-icon name="settings-outline"></ion-icon>-->
|
|
50
|
+
<!-- </ion-button>-->
|
|
51
|
+
<ion-button color="medium" title="Sign-out" (click)="logout($event)">
|
|
52
|
+
<ion-icon name="log-out-outline" />
|
|
53
|
+
</ion-button>
|
|
54
|
+
</ion-buttons>
|
|
55
|
+
</ion-item>
|
|
56
|
+
}
|
|
57
|
+
@if ($err()) {
|
|
58
|
+
<ion-item> ERROR: {{ $err() | json }}</ion-item>
|
|
59
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { JsonPipe } from '@angular/common';
|
|
2
|
+
import {
|
|
3
|
+
ChangeDetectionStrategy,
|
|
4
|
+
Component,
|
|
5
|
+
computed,
|
|
6
|
+
signal,
|
|
7
|
+
OnDestroy,
|
|
8
|
+
inject,
|
|
9
|
+
} from '@angular/core';
|
|
10
|
+
import { RouterModule } from '@angular/router';
|
|
11
|
+
import {
|
|
12
|
+
IonButton,
|
|
13
|
+
IonButtons,
|
|
14
|
+
IonIcon,
|
|
15
|
+
IonItem,
|
|
16
|
+
IonItemDivider,
|
|
17
|
+
IonLabel,
|
|
18
|
+
MenuController,
|
|
19
|
+
NavController,
|
|
20
|
+
} from '@ionic/angular/standalone';
|
|
21
|
+
import {
|
|
22
|
+
ISneatAuthState,
|
|
23
|
+
ISneatUserState,
|
|
24
|
+
SneatAuthStateService,
|
|
25
|
+
SneatUserService,
|
|
26
|
+
} from '@sneat/auth-core';
|
|
27
|
+
import { ErrorLogger, IErrorLogger } from '@sneat/core';
|
|
28
|
+
import { Subject, takeUntil } from 'rxjs';
|
|
29
|
+
import { PersonNamesPipe, personNames } from '../../pipes/person-names.pipe';
|
|
30
|
+
@Component({
|
|
31
|
+
selector: 'sneat-auth-menu-item',
|
|
32
|
+
templateUrl: './auth-menu-item.component.html',
|
|
33
|
+
imports: [
|
|
34
|
+
RouterModule,
|
|
35
|
+
PersonNamesPipe,
|
|
36
|
+
IonItemDivider,
|
|
37
|
+
IonLabel,
|
|
38
|
+
IonItem,
|
|
39
|
+
IonIcon,
|
|
40
|
+
IonButtons,
|
|
41
|
+
IonButton,
|
|
42
|
+
JsonPipe,
|
|
43
|
+
],
|
|
44
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
45
|
+
})
|
|
46
|
+
export class AuthMenuItemComponent implements OnDestroy {
|
|
47
|
+
private readonly errorLogger = inject<IErrorLogger>(ErrorLogger);
|
|
48
|
+
private readonly navCtrl = inject(NavController);
|
|
49
|
+
private readonly authStateService = inject(SneatAuthStateService);
|
|
50
|
+
private readonly menuController = inject(MenuController);
|
|
51
|
+
|
|
52
|
+
protected readonly $user = signal<ISneatUserState | undefined>(undefined);
|
|
53
|
+
protected $err = signal<unknown>(undefined);
|
|
54
|
+
|
|
55
|
+
protected readonly $authState = signal<ISneatAuthState | undefined>(
|
|
56
|
+
undefined,
|
|
57
|
+
);
|
|
58
|
+
protected readonly $authStatus = computed(() => this.$authState()?.status);
|
|
59
|
+
protected readonly $isAuthenticating = computed(
|
|
60
|
+
() => this.$authStatus() === 'authenticating',
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
protected readonly $destroyed = new Subject<void>();
|
|
64
|
+
|
|
65
|
+
public ngOnDestroy(): void {
|
|
66
|
+
this.$destroyed.next();
|
|
67
|
+
this.$destroyed.complete();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
constructor() {
|
|
71
|
+
const errorLogger = this.errorLogger;
|
|
72
|
+
const authStateService = this.authStateService;
|
|
73
|
+
const userService = inject(SneatUserService);
|
|
74
|
+
|
|
75
|
+
userService.userState
|
|
76
|
+
.pipe(takeUntil(this.$destroyed))
|
|
77
|
+
.subscribe(this.$user.set);
|
|
78
|
+
authStateService.authState.pipe(takeUntil(this.$destroyed)).subscribe({
|
|
79
|
+
next: this.$authState.set,
|
|
80
|
+
error: (err) => {
|
|
81
|
+
this.$err.set(err);
|
|
82
|
+
errorLogger.logError('failed to get auth state');
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
public closeMenu(): void {
|
|
88
|
+
this.menuController
|
|
89
|
+
.close()
|
|
90
|
+
.catch(this.errorLogger.logErrorHandler('Failed to close menu'));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
public logout(event: Event): boolean {
|
|
94
|
+
event.stopPropagation();
|
|
95
|
+
event.preventDefault();
|
|
96
|
+
try {
|
|
97
|
+
this.authStateService
|
|
98
|
+
.signOut()
|
|
99
|
+
.then(() => {
|
|
100
|
+
this.navCtrl
|
|
101
|
+
.navigateBack('/signed-out')
|
|
102
|
+
.catch(
|
|
103
|
+
this.errorLogger.logErrorHandler(
|
|
104
|
+
'Failed to navigate to signed out page',
|
|
105
|
+
),
|
|
106
|
+
);
|
|
107
|
+
})
|
|
108
|
+
.catch(this.errorLogger.logErrorHandler('Failed to sign out'));
|
|
109
|
+
} catch (e) {
|
|
110
|
+
this.errorLogger.logError(e, 'Failed to logout');
|
|
111
|
+
}
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
protected readonly personName = personNames;
|
|
116
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
<ion-card>
|
|
2
|
+
<!-- <ion-item color="light" lines="full">-->
|
|
3
|
+
<!-- <ion-label color="medium"-->
|
|
4
|
+
<!-- ><h2 style="font-weight: bold">Authentication</h2></ion-label-->
|
|
5
|
+
<!-- >-->
|
|
6
|
+
<!-- </ion-item>-->
|
|
7
|
+
<!-- <ion-accordion-group [value]="integrations">-->
|
|
8
|
+
<!-- <ion-accordion value="messaging-apps">-->
|
|
9
|
+
<!-- <ion-item slot="header" color="light">-->
|
|
10
|
+
<!-- <ion-label>First Accordion</ion-label>-->
|
|
11
|
+
<!-- </ion-item>-->
|
|
12
|
+
<!-- <div class="ion-padding" slot="content">First Content</div>-->
|
|
13
|
+
<!-- </ion-accordion>-->
|
|
14
|
+
<!-- <ion-accordion value="quick-logins">-->
|
|
15
|
+
<!-- <ion-item slot="header" color="light">-->
|
|
16
|
+
<!-- <ion-label>Second Accordion</ion-label>-->
|
|
17
|
+
<!-- </ion-item>-->
|
|
18
|
+
<!-- <div class="ion-padding" slot="content">Second Content</div>-->
|
|
19
|
+
<!-- </ion-accordion>-->
|
|
20
|
+
<!-- </ion-accordion-group>-->
|
|
21
|
+
<!-- <ion-item>-->
|
|
22
|
+
<!-- <ion-segment [(ngModel)]="integrations" style="margin-bottom: 0.5em">-->
|
|
23
|
+
<!-- <ion-segment-button value="messaging-apps"-->
|
|
24
|
+
<!-- >Messaging Apps-->
|
|
25
|
+
<!-- </ion-segment-button>-->
|
|
26
|
+
<!-- <ion-segment-button value="quick-logins">Quick logins</ion-segment-button>-->
|
|
27
|
+
<!-- </ion-segment>-->
|
|
28
|
+
<!-- </ion-item>-->
|
|
29
|
+
<ion-list>
|
|
30
|
+
<ion-item-divider color="light">
|
|
31
|
+
<ion-label color="medium" style="font-weight: bold"
|
|
32
|
+
>Quick logins</ion-label
|
|
33
|
+
>
|
|
34
|
+
</ion-item-divider>
|
|
35
|
+
<sneat-user-auth-provider-status
|
|
36
|
+
providerID="google.com"
|
|
37
|
+
[signingInWith]="signingInWith()"
|
|
38
|
+
(signingInWithChange)="signingInWith.set($event)"
|
|
39
|
+
/>
|
|
40
|
+
<sneat-user-auth-provider-status
|
|
41
|
+
providerID="apple.com"
|
|
42
|
+
[signingInWith]="signingInWith()"
|
|
43
|
+
(signingInWithChange)="signingInWith.set($event)"
|
|
44
|
+
/>
|
|
45
|
+
<sneat-user-auth-provider-status
|
|
46
|
+
providerID="microsoft.com"
|
|
47
|
+
[signingInWith]="signingInWith()"
|
|
48
|
+
(signingInWithChange)="signingInWith.set($event)"
|
|
49
|
+
/>
|
|
50
|
+
</ion-list>
|
|
51
|
+
<ion-list>
|
|
52
|
+
<ion-item-divider color="light">
|
|
53
|
+
<ion-label color="medium" style="font-weight: bold"
|
|
54
|
+
>Messaging apps</ion-label
|
|
55
|
+
>
|
|
56
|
+
</ion-item-divider>
|
|
57
|
+
|
|
58
|
+
<ion-item>
|
|
59
|
+
<ion-icon slot="start" name="paper-plane" color="primary"></ion-icon>
|
|
60
|
+
@if (!$userRecord()) {
|
|
61
|
+
<ion-label>
|
|
62
|
+
<h3>Telegram</h3>
|
|
63
|
+
<p>Checking....</p>
|
|
64
|
+
</ion-label>
|
|
65
|
+
} @else if (hasAccount("telegram")) {
|
|
66
|
+
<ion-label>
|
|
67
|
+
<h3 style="font-weight: bold">Telegram</h3>
|
|
68
|
+
<ion-text color="success" [title]="getAccountID('telegram')"
|
|
69
|
+
>Connected!
|
|
70
|
+
</ion-text>
|
|
71
|
+
</ion-label>
|
|
72
|
+
<ion-buttons slot="end">
|
|
73
|
+
<ion-button
|
|
74
|
+
[disabled]="disconnecting"
|
|
75
|
+
color="warning"
|
|
76
|
+
fill="solid"
|
|
77
|
+
(click)="disconnect('telegram')"
|
|
78
|
+
>
|
|
79
|
+
<ion-icon name="close-outline" slot="start"></ion-icon>
|
|
80
|
+
<ion-label>Disconnect</ion-label>
|
|
81
|
+
</ion-button>
|
|
82
|
+
</ion-buttons>
|
|
83
|
+
} @else {
|
|
84
|
+
<ion-label>
|
|
85
|
+
<h3>Telegram</h3>
|
|
86
|
+
<p>Not connected yet.</p>
|
|
87
|
+
</ion-label>
|
|
88
|
+
<ion-buttons slot="end">
|
|
89
|
+
<sneat-login-with-telegram [isUserAuthenticated]="true" />
|
|
90
|
+
</ion-buttons>
|
|
91
|
+
}
|
|
92
|
+
</ion-item>
|
|
93
|
+
<ion-item>
|
|
94
|
+
<ion-icon slot="start" name="logo-whatsapp"></ion-icon>
|
|
95
|
+
<ion-label color="medium">
|
|
96
|
+
<h3 style="font-weight: bold">WhatsApp</h3>
|
|
97
|
+
<p>Not implemented yet - coming soon!</p>
|
|
98
|
+
</ion-label>
|
|
99
|
+
<!-- <ion-buttons slot="end">-->
|
|
100
|
+
<!-- <ion-button color="danger">-->
|
|
101
|
+
<!-- <ion-icon name="close-circle-outline" slot="start"></ion-icon>-->
|
|
102
|
+
<!-- <ion-label>I have no WhatsApp</ion-label>-->
|
|
103
|
+
<!-- </ion-button>-->
|
|
104
|
+
<!-- </ion-buttons>-->
|
|
105
|
+
</ion-item>
|
|
106
|
+
<ion-item>
|
|
107
|
+
<ion-icon slot="start" name="logo-whatsapp"></ion-icon>
|
|
108
|
+
<!-- TODO: place Viber logo here -->
|
|
109
|
+
<ion-label color="medium">
|
|
110
|
+
<h3 style="font-weight: bold">Viber</h3>
|
|
111
|
+
<p>Not implemented yet - coming soon!</p>
|
|
112
|
+
</ion-label>
|
|
113
|
+
</ion-item>
|
|
114
|
+
</ion-list>
|
|
115
|
+
<ion-card-content>
|
|
116
|
+
<p>
|
|
117
|
+
You'll get much more out of Sneat.app if you integrate it with messaging
|
|
118
|
+
apps you use. Benefits:
|
|
119
|
+
</p>
|
|
120
|
+
<ul>
|
|
121
|
+
<li>Easily add contacts</li>
|
|
122
|
+
<li>Get reminders</li>
|
|
123
|
+
<li>Add todo/to_buy from your messenger</li>
|
|
124
|
+
<li>etc.</li>
|
|
125
|
+
</ul>
|
|
126
|
+
</ion-card-content>
|
|
127
|
+
</ion-card>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
|
2
|
+
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
|
3
|
+
import { SneatApiService } from '@sneat/api';
|
|
4
|
+
import { SneatUserService } from '@sneat/auth-core';
|
|
5
|
+
import { ErrorLogger } from '@sneat/core';
|
|
6
|
+
import { ClassName } from '@sneat/ui';
|
|
7
|
+
import { of } from 'rxjs';
|
|
8
|
+
import { UserAuthAccountsComponent } from './user-auth-accounts.component';
|
|
9
|
+
|
|
10
|
+
describe('UserAuthAccountsComponent', () => {
|
|
11
|
+
let component: UserAuthAccountsComponent;
|
|
12
|
+
let fixture: ComponentFixture<UserAuthAccountsComponent>;
|
|
13
|
+
|
|
14
|
+
beforeEach(waitForAsync(async () => {
|
|
15
|
+
await TestBed.configureTestingModule({
|
|
16
|
+
imports: [UserAuthAccountsComponent],
|
|
17
|
+
providers: [
|
|
18
|
+
{ provide: ClassName, useValue: 'TestComponent' },
|
|
19
|
+
{
|
|
20
|
+
provide: ErrorLogger,
|
|
21
|
+
useValue: { logError: vi.fn(), logErrorHandler: () => vi.fn() },
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
provide: SneatUserService,
|
|
25
|
+
useValue: {
|
|
26
|
+
userState: of({ record: undefined }),
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
provide: SneatApiService,
|
|
31
|
+
useValue: { delete: vi.fn() },
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
35
|
+
})
|
|
36
|
+
.overrideComponent(UserAuthAccountsComponent, {
|
|
37
|
+
set: { imports: [], schemas: [CUSTOM_ELEMENTS_SCHEMA] },
|
|
38
|
+
})
|
|
39
|
+
.compileComponents();
|
|
40
|
+
fixture = TestBed.createComponent(UserAuthAccountsComponent);
|
|
41
|
+
component = fixture.componentInstance;
|
|
42
|
+
}));
|
|
43
|
+
|
|
44
|
+
it('should create', () => {
|
|
45
|
+
expect(component).toBeTruthy();
|
|
46
|
+
});
|
|
47
|
+
});
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ChangeDetectionStrategy,
|
|
3
|
+
Component,
|
|
4
|
+
signal,
|
|
5
|
+
inject,
|
|
6
|
+
} from '@angular/core';
|
|
7
|
+
import { FormsModule } from '@angular/forms';
|
|
8
|
+
import {
|
|
9
|
+
IonButton,
|
|
10
|
+
IonButtons,
|
|
11
|
+
IonCard,
|
|
12
|
+
IonCardContent,
|
|
13
|
+
IonIcon,
|
|
14
|
+
IonItem,
|
|
15
|
+
IonItemDivider,
|
|
16
|
+
IonLabel,
|
|
17
|
+
IonList,
|
|
18
|
+
IonText,
|
|
19
|
+
} from '@ionic/angular/standalone';
|
|
20
|
+
import { SneatApiService } from '@sneat/api';
|
|
21
|
+
import { AuthProviderID, SneatUserService } from '@sneat/auth-core';
|
|
22
|
+
import { IUserRecord } from '@sneat/auth-models';
|
|
23
|
+
import { ClassName, SneatBaseComponent } from '@sneat/ui';
|
|
24
|
+
import { LoginWithTelegramComponent } from '../../pages/login-page/login-with-telegram.component';
|
|
25
|
+
import { UserAuthAProviderStatusComponent } from './user-auth-provider-status';
|
|
26
|
+
|
|
27
|
+
@Component({
|
|
28
|
+
selector: 'sneat-user-auth-accounts',
|
|
29
|
+
templateUrl: './user-auth-accounts.component.html',
|
|
30
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
31
|
+
imports: [
|
|
32
|
+
LoginWithTelegramComponent,
|
|
33
|
+
FormsModule,
|
|
34
|
+
UserAuthAProviderStatusComponent,
|
|
35
|
+
IonCard,
|
|
36
|
+
IonList,
|
|
37
|
+
IonItemDivider,
|
|
38
|
+
IonLabel,
|
|
39
|
+
IonItem,
|
|
40
|
+
IonIcon,
|
|
41
|
+
IonText,
|
|
42
|
+
IonButtons,
|
|
43
|
+
IonButton,
|
|
44
|
+
IonCardContent,
|
|
45
|
+
],
|
|
46
|
+
providers: [
|
|
47
|
+
{
|
|
48
|
+
provide: ClassName,
|
|
49
|
+
useValue: 'UserAuthAccountsComponent',
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
})
|
|
53
|
+
export class UserAuthAccountsComponent extends SneatBaseComponent {
|
|
54
|
+
private readonly sneatUserService = inject(SneatUserService);
|
|
55
|
+
private readonly sneatApiService = inject(SneatApiService);
|
|
56
|
+
|
|
57
|
+
protected readonly $userRecord = signal<IUserRecord | undefined>(undefined);
|
|
58
|
+
|
|
59
|
+
protected readonly signingInWith = signal<AuthProviderID | undefined>(
|
|
60
|
+
undefined,
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
constructor() {
|
|
64
|
+
super();
|
|
65
|
+
this.sneatUserService.userState.pipe(this.takeUntilDestroyed()).subscribe({
|
|
66
|
+
next: (user) => {
|
|
67
|
+
this.$userRecord.set(user.record || undefined);
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
protected hasAccount(provider: 'telegram' | 'viber' | 'whatsapp'): boolean {
|
|
73
|
+
provider = provider + ':';
|
|
74
|
+
for (const account of this.$userRecord()?.accounts || []) {
|
|
75
|
+
if (account.startsWith(provider)) {
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
protected getAccountID(provider: 'telegram'): string {
|
|
83
|
+
const prefix = provider + '::';
|
|
84
|
+
const a = this.$userRecord()?.accounts?.find((a) => a.startsWith(prefix));
|
|
85
|
+
return a?.replace(prefix, '') || '';
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
protected disconnecting?: 'telegram' | 'viber' | 'whatsapp';
|
|
89
|
+
|
|
90
|
+
protected disconnect(provider: 'telegram'): void {
|
|
91
|
+
if (
|
|
92
|
+
!confirm(
|
|
93
|
+
'Are you sure you want to disconnect Telegram login from your account?',
|
|
94
|
+
)
|
|
95
|
+
) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
this.disconnecting = provider;
|
|
99
|
+
this.sneatApiService
|
|
100
|
+
.delete('auth/disconnect?provider=' + provider)
|
|
101
|
+
.subscribe({
|
|
102
|
+
next: () => {
|
|
103
|
+
this.disconnecting = undefined;
|
|
104
|
+
alert('Disconnected!');
|
|
105
|
+
},
|
|
106
|
+
error: this.errorLogger.logErrorHandler('Failed to disconnect'),
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<ion-item>
|
|
2
|
+
<ion-icon [color]="provider().color" [name]="provider().icon" slot="start" />
|
|
3
|
+
<ion-label>
|
|
4
|
+
<h3>{{ provider().title || providerID() }}</h3>
|
|
5
|
+
<p>
|
|
6
|
+
@if(isConnected()) { Connected as
|
|
7
|
+
<b>{{authProviderUserInfo()?.displayName}}</b>
|
|
8
|
+
‐
|
|
9
|
+
<a href="mailto:{{authProviderUserInfo()?.email}}"
|
|
10
|
+
>{{authProviderUserInfo()?.email}}</a
|
|
11
|
+
>. } @else { Not connected yet. }
|
|
12
|
+
</p>
|
|
13
|
+
</ion-label>
|
|
14
|
+
<ion-buttons slot="end">
|
|
15
|
+
@if (isConnected()) {
|
|
16
|
+
<ion-button color="medium" fill="outline" (click)="disconnect()">
|
|
17
|
+
<ion-icon name="close-circle-outline" slot="start"></ion-icon>
|
|
18
|
+
<ion-label>
|
|
19
|
+
@if (isSigningIn()) { Disconnecting... } @else { Disconnect }
|
|
20
|
+
</ion-label>
|
|
21
|
+
</ion-button>
|
|
22
|
+
} @else {
|
|
23
|
+
<ion-button
|
|
24
|
+
[disabled]="isDisabled()"
|
|
25
|
+
[color]="provider().color"
|
|
26
|
+
[fill]="'solid'"
|
|
27
|
+
(click)="connect()"
|
|
28
|
+
>
|
|
29
|
+
@if (isSigningIn()) {
|
|
30
|
+
<ion-spinner slot="start" name="lines-small" />
|
|
31
|
+
} @else {
|
|
32
|
+
<ion-icon slot="start" [name]="provider().icon" />
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
<ion-label>
|
|
36
|
+
@if (signingInWith() === providerID()) { Signing in... } @else { Connect
|
|
37
|
+
}
|
|
38
|
+
</ion-label>
|
|
39
|
+
</ion-button>
|
|
40
|
+
}
|
|
41
|
+
</ion-buttons>
|
|
42
|
+
</ion-item>
|