feat: add providers OAuth
This commit is contained in:
parent
a2d4151cc3
commit
86e6f59567
@ -1,8 +1,14 @@
|
||||
import {Injectable} from "@angular/core";
|
||||
import ApiService, {AvailableVersion} from "@api/api.service";
|
||||
import {LoginRequest} from "@api/v1/loginRequest";
|
||||
import {catchError, of} from "rxjs";
|
||||
import {AuthRoles} from "@model/AuthRoles";
|
||||
import {catchError, map, Observable, of} from "rxjs";
|
||||
import {AuthRoles} from "@model/authRoles";
|
||||
import {AvailableOAuthProvidersResponse} from "@api/v1/availableProvidersResponse";
|
||||
import {OAuthProvider} from "@model/oAuthProvider";
|
||||
|
||||
export interface OAuthProviderData extends AvailableOAuthProvidersResponse {
|
||||
icon: string;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
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