feat: add providers OAuth
This commit is contained in:
parent
a2d4151cc3
commit
86e6f59567
@ -1,8 +1,14 @@
|
|||||||
import {Injectable} from "@angular/core";
|
import {Injectable} from "@angular/core";
|
||||||
import ApiService, {AvailableVersion} from "@api/api.service";
|
import ApiService, {AvailableVersion} from "@api/api.service";
|
||||||
import {LoginRequest} from "@api/v1/loginRequest";
|
import {LoginRequest} from "@api/v1/loginRequest";
|
||||||
import {catchError, of} from "rxjs";
|
import {catchError, map, Observable, of} from "rxjs";
|
||||||
import {AuthRoles} from "@model/AuthRoles";
|
import {AuthRoles} from "@model/authRoles";
|
||||||
|
import {AvailableOAuthProvidersResponse} from "@api/v1/availableProvidersResponse";
|
||||||
|
import {OAuthProvider} from "@model/oAuthProvider";
|
||||||
|
|
||||||
|
export interface OAuthProviderData extends AvailableOAuthProvidersResponse {
|
||||||
|
icon: string;
|
||||||
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export default class AuthApiService extends ApiService {
|
export default class AuthApiService extends ApiService {
|
||||||
@ -51,4 +57,32 @@ export default class AuthApiService extends ApiService {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getProviderIcon(provider: OAuthProvider): string {
|
||||||
|
switch (provider) {
|
||||||
|
case OAuthProvider.Google:
|
||||||
|
return 'assets/icons/google.svg';
|
||||||
|
case OAuthProvider.Yandex:
|
||||||
|
return 'assets/icons/yandex.svg';
|
||||||
|
case OAuthProvider.MailRu:
|
||||||
|
return 'assets/icons/mailru.svg';
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public availableProviders(): Observable<OAuthProviderData[]> {
|
||||||
|
let request = this.createRequestBuilder()
|
||||||
|
.setEndpoint('AvailableProviders')
|
||||||
|
.setWithCredentials()
|
||||||
|
.build;
|
||||||
|
|
||||||
|
return this.get<Array<AvailableOAuthProvidersResponse>>(request).pipe(
|
||||||
|
map(data => {
|
||||||
|
return data.map((provider) => ({
|
||||||
|
...provider,
|
||||||
|
icon: this.getProviderIcon(provider.provider),
|
||||||
|
}) as OAuthProviderData);
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
77
src/components/OAuthProviders/OAuthProviders.css
Normal file
77
src/components/OAuthProviders/OAuthProviders.css
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
.provider-container {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.provider-container a {
|
||||||
|
display: inline-block;
|
||||||
|
text-align: center;
|
||||||
|
transition: transform 0.3s ease-in-out, filter 0.3s ease;
|
||||||
|
border-radius: 50%;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.provider-icon {
|
||||||
|
object-fit: contain;
|
||||||
|
user-select: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 0.3s ease-in-out, box-shadow 0.3s ease, filter 0.3s ease;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.provider-container a:hover .provider-icon {
|
||||||
|
transform: scale(1.1); /* Slight zoom-in effect on hover */
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); /* Adding shadow for effect */
|
||||||
|
}
|
||||||
|
|
||||||
|
.provider-container .provider-item.disabled {
|
||||||
|
pointer-events: none; /* Disables click */
|
||||||
|
opacity: 0.5; /* Dims the icon to indicate it is disabled */
|
||||||
|
}
|
||||||
|
|
||||||
|
.provider-container .provider-item.disabled .provider-icon {
|
||||||
|
filter: grayscale(100%) contrast(100%); /* Desaturates image if disabled */
|
||||||
|
}
|
||||||
|
|
||||||
|
.provider-item {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.provider-item.provider-unlink {
|
||||||
|
filter: grayscale(50%) contrast(60%);
|
||||||
|
transition: filter 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.provider-item.provider-unlink:hover {
|
||||||
|
filter: grayscale(0%) contrast(100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.provider-item.provider-unlink::after {
|
||||||
|
content: '×';
|
||||||
|
position: absolute;
|
||||||
|
top: 10%;
|
||||||
|
left: 90%;
|
||||||
|
transform: translate(-100%, 0%) scale(0.0);
|
||||||
|
font-size: 24px;
|
||||||
|
color: white;
|
||||||
|
background-color: red;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
transition: transform 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.provider-item.provider-unlink:hover::after {
|
||||||
|
transform: translate(-50%, -50%) scale(1.0);
|
||||||
|
}
|
16
src/components/OAuthProviders/OAuthProviders.html
Normal file
16
src/components/OAuthProviders/OAuthProviders.html
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
@if (providers.length !== 0) {
|
||||||
|
<hr/>
|
||||||
|
<div>
|
||||||
|
<p class="mat-body-2 secondary">{{ message }}</p>
|
||||||
|
|
||||||
|
<div class="provider-container">
|
||||||
|
@for (provider of providers; track $index) {
|
||||||
|
<a class="provider-item" (click)="provider.disabled ? confirmDelete(provider) : openOAuth(provider)"
|
||||||
|
[class.disabled]="!canUnlink && provider.disabled" [class.provider-unlink]="canUnlink && provider.disabled">
|
||||||
|
<img [alt]="provider.providerName" [src]="provider.icon"
|
||||||
|
class="provider-icon" draggable="false"/>
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
126
src/components/OAuthProviders/OAuthProviders.ts
Normal file
126
src/components/OAuthProviders/OAuthProviders.ts
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
import {Component, Inject, Input, OnInit} from '@angular/core';
|
||||||
|
import AuthApiService, {OAuthProviderData} from "@api/v1/authApiService";
|
||||||
|
import {OAuthProvider} from "@model/oAuthProvider";
|
||||||
|
import {ToastrService} from "ngx-toastr";
|
||||||
|
import {
|
||||||
|
MAT_DIALOG_DATA, MatDialog,
|
||||||
|
MatDialogActions,
|
||||||
|
MatDialogContent,
|
||||||
|
MatDialogRef,
|
||||||
|
MatDialogTitle
|
||||||
|
} from "@angular/material/dialog";
|
||||||
|
import {MatButton} from "@angular/material/button";
|
||||||
|
|
||||||
|
interface AvailableOAuthProviders extends OAuthProviderData {
|
||||||
|
disabled: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-delete-confirm-dialog',
|
||||||
|
template: `
|
||||||
|
<h1 mat-dialog-title>Удалить провайдера?</h1>
|
||||||
|
<mat-dialog-content>
|
||||||
|
<p>Вы уверены, что хотите удалить провайдера {{ data.provider.name }}?</p>
|
||||||
|
</mat-dialog-content>
|
||||||
|
<mat-dialog-actions style="display: flex; justify-content: flex-end;">
|
||||||
|
<button mat-button (click)="onCancel()">Отмена</button>
|
||||||
|
<button mat-raised-button color="warn" (click)="onConfirm()">Удалить</button>
|
||||||
|
</mat-dialog-actions>
|
||||||
|
`,
|
||||||
|
imports: [
|
||||||
|
MatDialogTitle,
|
||||||
|
MatDialogContent,
|
||||||
|
MatDialogActions,
|
||||||
|
MatButton
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class DeleteConfirmDialog {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public dialogRef: MatDialogRef<DeleteConfirmDialog>,
|
||||||
|
@Inject(MAT_DIALOG_DATA) public data: any
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
onConfirm(): void {
|
||||||
|
this.dialogRef.close(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
onCancel(): void {
|
||||||
|
this.dialogRef.close(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'OAuthProviders',
|
||||||
|
imports: [],
|
||||||
|
templateUrl: './OAuthProviders.html',
|
||||||
|
styleUrl: './OAuthProviders.css',
|
||||||
|
providers: [AuthApiService]
|
||||||
|
})
|
||||||
|
export class OAuthProviders implements OnInit {
|
||||||
|
protected providers: AvailableOAuthProviders[] = [];
|
||||||
|
protected _activeProvidersId: OAuthProvider[] = [];
|
||||||
|
|
||||||
|
@Input() message: string = 'Вы можете войти в аккаунт через';
|
||||||
|
@Input() activeProviders: string[] = [];
|
||||||
|
|
||||||
|
@Input() set activeProvidersId(data: OAuthProvider[]) {
|
||||||
|
this._activeProvidersId = data;
|
||||||
|
this.updateDisabledProviders();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Input() canUnlink: boolean = false;
|
||||||
|
|
||||||
|
constructor(authApi: AuthApiService, private notify: ToastrService, private dialog: MatDialog) {
|
||||||
|
authApi.availableProviders().subscribe(providers => this.updateDisabledProviders(providers));
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateDisabledProviders(data: OAuthProviderData[] | null = null) {
|
||||||
|
this.providers = (data ?? this.providers).map(provider => {
|
||||||
|
return {
|
||||||
|
...provider,
|
||||||
|
disabled: this._activeProvidersId.includes(provider.provider) || this.activeProviders.includes(provider.providerName)
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
window.addEventListener('message', (event) => {
|
||||||
|
if (event.data && event.data.success === false) {
|
||||||
|
console.error(event.data.message);
|
||||||
|
this.notify.error(event.data.message, 'OAuth ошибка');
|
||||||
|
} else {
|
||||||
|
this.activeProvidersId.push(event.data.provider);
|
||||||
|
this.updateDisabledProviders();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected openOAuth(provider: AvailableOAuthProviders) {
|
||||||
|
console.log(provider.redirect);
|
||||||
|
const oauthWindow = window.open(
|
||||||
|
provider.redirect,
|
||||||
|
'_blank',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!oauthWindow) {
|
||||||
|
this.notify.error('Не удалось открыть OAuth окно');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected confirmDelete(provider: AvailableOAuthProviders) {
|
||||||
|
const dialogRef = this.dialog.open(DeleteConfirmDialog, {data: {provider}});
|
||||||
|
|
||||||
|
dialogRef.afterClosed().subscribe(result => {
|
||||||
|
if (result) {
|
||||||
|
this.deleteProvider(provider);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected deleteProvider(provider: AvailableOAuthProviders) {
|
||||||
|
// todo: remove provider
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user