diff --git a/apps/angular/src/app/features/auth/login/login.component.ts b/apps/angular/src/app/features/auth/login/login.component.ts index 62428eb..dce99dd 100644 --- a/apps/angular/src/app/features/auth/login/login.component.ts +++ b/apps/angular/src/app/features/auth/login/login.component.ts @@ -46,8 +46,6 @@ type LoginForm = { changeDetection: ChangeDetectionStrategy.OnPush, }) export class LoginComponent { - private readonly fb = inject(NonNullableFormBuilder); - /** Login form. */ protected readonly loginForm: FormGroup; @@ -63,6 +61,8 @@ export class LoginComponent { private readonly router = inject(Router); + private readonly fb = inject(NonNullableFormBuilder); + public constructor() { this.loginForm = this.initializeForm(); } diff --git a/apps/angular/src/app/features/auth/registration/registration.component.ts b/apps/angular/src/app/features/auth/registration/registration.component.ts index 7a4a86c..debac9e 100644 --- a/apps/angular/src/app/features/auth/registration/registration.component.ts +++ b/apps/angular/src/app/features/auth/registration/registration.component.ts @@ -52,14 +52,6 @@ type RegistrationForm = { changeDetection: ChangeDetectionStrategy.OnPush, }) export class RegistrationComponent { - private readonly fb = inject(NonNullableFormBuilder); - - private readonly destroyRef = inject(DestroyRef); - - private readonly userService = inject(UserService); - - private readonly router = inject(Router); - /** Register form group. */ protected readonly registrationForm: FormGroup; @@ -69,6 +61,14 @@ export class RegistrationComponent { /** Loading state. */ protected readonly isLoading$ = new BehaviorSubject(false); + private readonly fb = inject(NonNullableFormBuilder); + + private readonly destroyRef = inject(DestroyRef); + + private readonly userService = inject(UserService); + + private readonly router = inject(Router); + public constructor() { this.registrationForm = this.initializeForm(); } diff --git a/apps/angular/src/core/mappers/api-error-response.mapper.ts b/apps/angular/src/core/mappers/api-error-response.mapper.ts index 04b8d25..a8d9193 100644 --- a/apps/angular/src/core/mappers/api-error-response.mapper.ts +++ b/apps/angular/src/core/mappers/api-error-response.mapper.ts @@ -10,7 +10,7 @@ const DEFAULT_ERROR_MESSAGE = 'Something went wrong. Please try again later.'; const AUTH_ERROR_CODES = ['no_active_account']; const AUTH_ERROR_ATTRS = ['email', 'password', 'first_name', 'last_name', 'avatar']; -/** Api response mapper. */ +/** Api error response mapper. */ @Injectable({ providedIn: 'root' }) export class ApiErrorResponseMapper implements TMapperFromDto { diff --git a/apps/angular/src/core/mappers/registration.mapper.ts b/apps/angular/src/core/mappers/registration.mapper.ts index b866ed3..723ca49 100644 --- a/apps/angular/src/core/mappers/registration.mapper.ts +++ b/apps/angular/src/core/mappers/registration.mapper.ts @@ -3,7 +3,7 @@ import { RegistrationDto } from '@js-camp/core/dtos/registration.dto'; import { TMapperToDto } from '@js-camp/core/models/mapper'; import { Registration } from '@js-camp/core/models/registration'; -/** Login mapper. */ +/** Registration mapper. */ @Injectable({ providedIn: 'root', }) diff --git a/apps/angular/src/core/services/form-error.service.ts b/apps/angular/src/core/services/form-error.service.ts index 49cffd4..338244c 100644 --- a/apps/angular/src/core/services/form-error.service.ts +++ b/apps/angular/src/core/services/form-error.service.ts @@ -32,6 +32,37 @@ export class FormErrorService { } } + /** + * Get the errors of the control. + * @param control Form control. + */ + public getErrors(control: AbstractControl): string { + const errors = { ...control.errors, ...control.parent?.errors }; + if (!errors) { + return ''; + } + + return Object.keys(errors) + .map(errorKey => this.getErrorMessage(errorKey)) + .join(' '); + } + + /** + * Clears errors for a specific field or the entire form. + * @param form Form group. + * @param fieldName Specific field name. + */ + public clearErrors(form: FormGroup, fieldName?: string): void { + if (fieldName) { + const control = this.findFieldControl(form, fieldName); + if (control) { + control.setErrors(null); + } + } else { + form.updateValueAndValidity(); + } + } + private displayResponseError(form: FormGroup, apiErrorResponse: ApiErrorResponse): void { if (apiErrorResponse.errors.length === 0) { this.notificationService.showMessage(ERROR_MESSAGES['default']); @@ -83,35 +114,4 @@ export class FormErrorService { const control = this.findFieldControl(form, fieldName); return control != null; } - - /** - * Get the errors of the control. - * @param control Form control. - */ - public getErrors(control: AbstractControl): string { - const errors = { ...control.errors, ...control.parent?.errors }; - if (!errors) { - return ''; - } - - return Object.keys(errors) - .map(errorKey => this.getErrorMessage(errorKey)) - .join(' '); - } - - /** - * Clears errors for a specific field or the entire form. - * @param form Form group. - * @param fieldName Specific field name. - */ - public clearErrors(form: FormGroup, fieldName?: string): void { - if (fieldName) { - const control = this.findFieldControl(form, fieldName); - if (control) { - control.setErrors(null); - } - } else { - form.updateValueAndValidity(); - } - } } diff --git a/apps/angular/src/core/services/notification.service.ts b/apps/angular/src/core/services/notification.service.ts index 9579c3e..5acbded 100644 --- a/apps/angular/src/core/services/notification.service.ts +++ b/apps/angular/src/core/services/notification.service.ts @@ -13,7 +13,6 @@ const DEFAULT_MILLISECOND = 1000; providedIn: 'root', }) export class NotificationService { - /** Snackbar instance. */ private readonly snackBar = inject(MatSnackBar); /** diff --git a/apps/angular/src/core/services/storage.service.ts b/apps/angular/src/core/services/storage.service.ts index b3b8956..f7115c7 100644 --- a/apps/angular/src/core/services/storage.service.ts +++ b/apps/angular/src/core/services/storage.service.ts @@ -6,7 +6,6 @@ import { BehaviorSubject, Observable, defer, filter, fromEvent, map, merge, of, providedIn: 'root', }) export class StorageService { - /** Emits the key of the changed value. */ private readonly valueChangedSubject$ = new BehaviorSubject(''); private readonly localStorage = window.localStorage; @@ -38,6 +37,18 @@ export class StorageService { ); } + /** + * Removed data from storage. + * @param key Key. + */ + public remove(key: string): Observable { + return defer(() => { + this.localStorage.removeItem(key); + this.valueChangedSubject$.next(key); + return of(undefined); + }); + } + private watchStorageChangeByKey(keyToWatch: string): Observable { const otherPageChange$ = fromEvent(window, 'storage').pipe( filter((event): event is StorageEvent => event instanceof StorageEvent), @@ -66,16 +77,4 @@ export class StorageService { return null; } } - - /** - * Removed data from storage. - * @param key Key. - */ - public remove(key: string): Observable { - return defer(() => { - this.localStorage.removeItem(key); - this.valueChangedSubject$.next(key); - return of(undefined); - }); - } } diff --git a/apps/angular/src/core/services/user-secret-storage.service.ts b/apps/angular/src/core/services/user-secret-storage.service.ts index ea5e4cc..e5f3734 100644 --- a/apps/angular/src/core/services/user-secret-storage.service.ts +++ b/apps/angular/src/core/services/user-secret-storage.service.ts @@ -12,11 +12,11 @@ const USER_SECRET_STORAGE_KEY = 'user'; providedIn: 'root', }) export class UserSecretStorageService { - private readonly storageService = inject(StorageService); - /** Secret info of current user. */ public readonly currentSecret$: Observable; + private readonly storageService = inject(StorageService); + public constructor() { this.currentSecret$ = this.storageService.get(USER_SECRET_STORAGE_KEY); } diff --git a/apps/angular/src/core/services/user.service.ts b/apps/angular/src/core/services/user.service.ts index 389f53b..ad56a89 100644 --- a/apps/angular/src/core/services/user.service.ts +++ b/apps/angular/src/core/services/user.service.ts @@ -32,40 +32,21 @@ import { UserSecretStorageService } from './user-secret-storage.service'; providedIn: 'root', }) export class UserService { - private readonly authService = inject(AuthService); - - private readonly userApiService = inject(UserApiService); - - private readonly userSecretStorage = inject(UserSecretStorageService); - /** Current user. `null` when a user is not logged in. */ public readonly currentUser$: Observable; /** Whether the current user is authorized. */ public readonly isAuthorized$: Observable; - public constructor() { - this.currentUser$ = this.initCurrentUserStream(); - this.isAuthorized$ = this.currentUser$.pipe(map(user => user != null)); - } + private readonly authService = inject(AuthService); - private saveSecretAndWaitForAuthorized(): OperatorFunction { - return pipe( - switchMap(secret => { - const saveUserSecretSideEffect$ = this.userSecretStorage.saveSecret(secret).pipe(ignoreElements()); + private readonly userApiService = inject(UserApiService); - return merge(this.isAuthorized$, saveUserSecretSideEffect$); - }), - first(isAuthorized => isAuthorized), - map(() => undefined), - ); - } + private readonly userSecretStorage = inject(UserSecretStorageService); - private initCurrentUserStream(): Observable { - return this.userSecretStorage.currentSecret$.pipe( - switchMap(secret => (secret ? this.userApiService.getCurrentUser() : of(null))), - shareReplay({ bufferSize: 1, refCount: false }), - ); + public constructor() { + this.currentUser$ = this.initCurrentUserStream(); + this.isAuthorized$ = this.currentUser$.pipe(map(user => user != null)); } /** @@ -100,4 +81,23 @@ export class UserService { map(() => undefined), ); } + + private saveSecretAndWaitForAuthorized(): OperatorFunction { + return pipe( + switchMap(secret => { + const saveUserSecretSideEffect$ = this.userSecretStorage.saveSecret(secret).pipe(ignoreElements()); + + return merge(this.isAuthorized$, saveUserSecretSideEffect$); + }), + first(isAuthorized => isAuthorized), + map(() => undefined), + ); + } + + private initCurrentUserStream(): Observable { + return this.userSecretStorage.currentSecret$.pipe( + switchMap(secret => (secret ? this.userApiService.getCurrentUser() : of(null))), + shareReplay({ bufferSize: 1, refCount: false }), + ); + } } diff --git a/apps/angular/src/shared/components/password-input/password-input.component.ts b/apps/angular/src/shared/components/password-input/password-input.component.ts index 6018a7a..c7f1631 100644 --- a/apps/angular/src/shared/components/password-input/password-input.component.ts +++ b/apps/angular/src/shared/components/password-input/password-input.component.ts @@ -27,7 +27,6 @@ import { tap } from 'rxjs'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class PasswordInputComponent implements OnInit { - /** Password form control. */ @Input() public control = new FormControl(); @@ -44,16 +43,16 @@ export class PasswordInputComponent implements OnInit { @Input() public id = ''; - private readonly changeDetector = inject(ChangeDetectorRef); - - private readonly destroyRef = inject(DestroyRef); - /** Form error. */ protected readonly formErrorService = inject(FormErrorService); /** Hide password flag. */ protected readonly hidePassword = signal(true); + private readonly changeDetector = inject(ChangeDetectorRef); + + private readonly destroyRef = inject(DestroyRef); + /** Side effect to ensure checking the input when its status changed. */ public ngOnInit(): void { this.control.statusChanges