Repository: ita-social-projects/OoS-Frontend
Pull request #2859: profile-waiting and forbidden pages
Author: login: YakymivY
Created: 2025-04-14T09:49:18Z | Merged: 2025-05-13T07:26:41Z

created a template to show while GetProfile data is fetching from server, a page to show when the user is forbidden to enter his account

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Introduced a dedicated forbidden page with localized messages and a logout option for unauthorized access.
  - Added a profile waiting page with a loading spinner and internationalized messages during authorization checks.
  - Implemented role-based user profile retrieval and caching, improving user state management.
  - Added user profile and personal information to the application state for enhanced data handling.

- **Improvements**
  - Enhanced loading experience by conditionally displaying a waiting page during authorization.
  - Streamlined error handling and navigation for forbidden access scenarios.

- **Bug Fixes**
  - Ensured correct prototype assignment for carousel images in competition and provider details.

- **Style**
  - Added and updated styles for forbidden and waiting pages to improve layout and accessibility.

- **Tests**
  - Added comprehensive tests for new user profile service, forbidden page, and waiting page components.

- **Chores**
  - Updated routing to replace server error page with forbidden page and removed unused routes.
  - Integrated progress spinner module for improved loading indicators.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/src/app/app.component.html b/src/app/app.component.html
index 65bc19de25..e4ee4b132c 100644
--- a/src/app/app.component.html
+++ b/src/app/app.component.html
@@ -1,11 +1,17 @@
 <mat-drawer-container>
   <div class="flex flex-col">
-    <div class="content" [ngClass]="{ 'hide-content': (isAuthorizationLoading$ | async) }">
-      <app-sidenav-menu [isMobileView]="isMobileView"></app-sidenav-menu>
-      <app-sidenav-filters [isMobileView]="isMobileView"></app-sidenav-filters>
-      <app-header></app-header>
-      <app-shell></app-shell>
-    </div>
-    <app-footer></app-footer>
+    <ng-container *ngIf="isAuthorizationLoading$ | async; else content">
+      <app-profile-waiting-page></app-profile-waiting-page>
+    </ng-container>
+
+    <ng-template #content>
+      <div class="content">
+        <app-sidenav-menu [isMobileView]="isMobileView"></app-sidenav-menu>
+        <app-sidenav-filters [isMobileView]="isMobileView"></app-sidenav-filters>
+        <app-header></app-header>
+        <app-shell></app-shell>
+      </div>
+      <app-footer></app-footer>
+    </ng-template>
   </div>
 </mat-drawer-container>
diff --git a/src/app/app.component.scss b/src/app/app.component.scss
index b40a9d9a8b..eb9aa0804e 100644
--- a/src/app/app.component.scss
+++ b/src/app/app.component.scss
@@ -3,10 +3,6 @@
   box-sizing: border-box;
 }
 
-.hide-content {
-  visibility: hidden;
-}
-
 .content {
   min-height: calc(100vh - 121px);
   flex-grow: 1;
diff --git a/src/app/shared/components/forbidden-page/forbidden-page.component.html b/src/app/shared/components/forbidden-page/forbidden-page.component.html
new file mode 100644
index 0000000000..0a25a55484
--- /dev/null
+++ b/src/app/shared/components/forbidden-page/forbidden-page.component.html
@@ -0,0 +1,7 @@
+<div class="flex flex-col justify-center items-center">
+  <div class="forbidden-container flex flex-col justify-center items-start">
+    <h1 class="forbidden-title">{{ 'FORBIDDEN_PAGE.SORRY' | translate }}</h1>
+    <h2 class="forbidden-text">{{ 'FORBIDDEN_PAGE.FORBIDDEN' | translate }}</h2>
+    <button class="btn" (click)="onBack()" mat-raised-button data-testid="button">{{ 'BUTTONS.GO_BACK' | translate }}</button>
+  </div>
+</div>
diff --git a/src/app/shared/components/forbidden-page/forbidden-page.component.scss b/src/app/shared/components/forbidden-page/forbidden-page.component.scss
new file mode 100644
index 0000000000..44f546910e
--- /dev/null
+++ b/src/app/shared/components/forbidden-page/forbidden-page.component.scss
@@ -0,0 +1,31 @@
+@import 'src/app/shared/styles/components/buttons.scss';
+
+.forbidden-container {
+  width: 716px;
+  background-color: #ffffff;
+  border-radius: 0.5rem;
+  padding: 36px 24px;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  gap: 10px;
+  box-shadow: 0px 6px 16px 0px #00000014;
+}
+
+.forbidden-title {
+  font-weight: 800;
+  font-size: 24px;
+  line-height: 24px;
+  color: #444444;
+}
+
+.forbidden-text {
+  font-size: 16px;
+  font-weight: 700;
+  color: #444444;
+}
+
+.btn {
+  padding: 9px 16px;
+}
diff --git a/src/app/shared/components/forbidden-page/forbidden-page.component.spec.ts b/src/app/shared/components/forbidden-page/forbidden-page.component.spec.ts
new file mode 100644
index 0000000000..b9b3e144bd
--- /dev/null
+++ b/src/app/shared/components/forbidden-page/forbidden-page.component.spec.ts
@@ -0,0 +1,38 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { Store } from '@ngxs/store';
+import { Logout } from 'shared/store/registration.actions';
+import { of } from 'rxjs';
+import { TranslateModule } from '@ngx-translate/core';
+import { ForbiddenPageComponent } from './forbidden-page.component';
+
+describe('ForbiddenPageComponent', () => {
+  let component: ForbiddenPageComponent;
+  let fixture: ComponentFixture<ForbiddenPageComponent>;
+  let storeMock: { dispatch: jest.Mock };
+
+  beforeEach(async () => {
+    storeMock = {
+      dispatch: jest.fn().mockReturnValue(of(true))
+    };
+
+    await TestBed.configureTestingModule({
+      imports: [TranslateModule.forRoot()],
+      declarations: [ForbiddenPageComponent],
+      providers: [{ provide: Store, useValue: storeMock }]
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(ForbiddenPageComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create the component', () => {
+    expect(component).toBeTruthy();
+  });
+
+  it('should dispatch Logout action on onBack()', () => {
+    component.onBack();
+    expect(storeMock.dispatch).toHaveBeenCalledWith(new Logout());
+    expect(storeMock.dispatch).toHaveBeenCalledTimes(1);
+  });
+});
diff --git a/src/app/shared/components/forbidden-page/forbidden-page.component.ts b/src/app/shared/components/forbidden-page/forbidden-page.component.ts
new file mode 100644
index 0000000000..15baa1d221
--- /dev/null
+++ b/src/app/shared/components/forbidden-page/forbidden-page.component.ts
@@ -0,0 +1,16 @@
+import { Component } from '@angular/core';
+import { Store } from '@ngxs/store';
+import { Logout } from 'shared/store/registration.actions';
+
+@Component({
+  selector: 'app-forbidden-page',
+  templateUrl: './forbidden-page.component.html',
+  styleUrls: ['./forbidden-page.component.scss']
+})
+export class ForbiddenPageComponent {
+  constructor(private readonly store: Store) {}
+
+  public onBack(): void {
+    this.store.dispatch(new Logout());
+  }
+}
diff --git a/src/app/shared/components/profile-waiting-page/profile-waiting-page.component.html b/src/app/shared/components/profile-waiting-page/profile-waiting-page.component.html
new file mode 100644
index 0000000000..bf45aad237
--- /dev/null
+++ b/src/app/shared/components/profile-waiting-page/profile-waiting-page.component.html
@@ -0,0 +1,10 @@
+<div class="flex flex-col justify-center items-center">
+  <div class="waiting-container flex flex-col justify-center items-center">
+    <div class="waiting-content flex flex-col justify-center items-center">
+      <h1 class="waiting-title-main">{{ 'WAITING_PAGE.WAIT' | translate }}</h1>
+      <h2 class="waiting-title-secondary">{{ 'WAITING_PAGE.GETTING_PROFILE' | translate }}</h2>
+      <mat-progress-spinner mode="indeterminate" color="primary" [diameter]="36"></mat-progress-spinner>
+      <p class="waiting-duration-text">{{ 'WAITING_PAGE.WAIT_A_MINUTE' | translate }}</p>
+    </div>
+  </div>
+</div>
diff --git a/src/app/shared/components/profile-waiting-page/profile-waiting-page.component.scss b/src/app/shared/components/profile-waiting-page/profile-waiting-page.component.scss
new file mode 100644
index 0000000000..db23d44dfa
--- /dev/null
+++ b/src/app/shared/components/profile-waiting-page/profile-waiting-page.component.scss
@@ -0,0 +1,32 @@
+.waiting-container {
+  width: 716px;
+  background-color: #ffffff;
+  border-radius: 8px;
+  padding: 36px 24px;
+  box-shadow: 0px 6px 16px 0px #00000014;
+  gap: 10px;
+  margin-top: 100px;
+}
+
+.waiting-content {
+  gap: 18px;
+}
+
+.waiting-title-main {
+  font-size: 24px;
+  font-weight: 800;
+  color: #444444;
+}
+
+.waiting-title-secondary {
+  font-size: 16px;
+  font-weight: 700;
+  color: #444444;
+}
+
+.waiting-duration-text {
+  font-size: 13px;
+  color: #888888;
+  line-height: 16px;
+  margin: 0;
+}
diff --git a/src/app/shared/components/profile-waiting-page/profile-waiting-page.component.spec.ts b/src/app/shared/components/profile-waiting-page/profile-waiting-page.component.spec.ts
new file mode 100644
index 0000000000..27ea5b09d1
--- /dev/null
+++ b/src/app/shared/components/profile-waiting-page/profile-waiting-page.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { TranslateModule } from '@ngx-translate/core';
+import { ProfileWaitingPageComponent } from './profile-waiting-page.component';
+
+describe('ProfileWaitingPageComponent', () => {
+  let component: ProfileWaitingPageComponent;
+  let fixture: ComponentFixture<ProfileWaitingPageComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [TranslateModule.forRoot()],
+      declarations: [ProfileWaitingPageComponent]
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(ProfileWaitingPageComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/src/app/shared/components/profile-waiting-page/profile-waiting-page.component.ts b/src/app/shared/components/profile-waiting-page/profile-waiting-page.component.ts
new file mode 100644
index 0000000000..0e8d5035b6
--- /dev/null
+++ b/src/app/shared/components/profile-waiting-page/profile-waiting-page.component.ts
@@ -0,0 +1,8 @@
+import { Component } from '@angular/core';
+
+@Component({
+  selector: 'app-profile-waiting-page',
+  templateUrl: './profile-waiting-page.component.html',
+  styleUrls: ['./profile-waiting-page.component.scss']
+})
+export class ProfileWaitingPageComponent {}
diff --git a/src/app/shared/modules/material.module.ts b/src/app/shared/modules/material.module.ts
index bb632f9809..8113d97041 100644
--- a/src/app/shared/modules/material.module.ts
+++ b/src/app/shared/modules/material.module.ts
@@ -27,6 +27,7 @@ import { MatSidenavModule } from '@angular/material/sidenav';
 import { MatSortModule } from '@angular/material/sort';
 import { MatStepperModule } from '@angular/material/stepper';
 import { MatSlideToggleModule } from '@angular/material/slide-toggle';
+import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
 
 @NgModule({
   exports: [
@@ -58,7 +59,8 @@ import { MatSlideToggleModule } from '@angular/material/slide-toggle';
     MatTableModule,
     MatSortModule,
     MatExpansionModule,
-    MatSlideToggleModule
+    MatSlideToggleModule,
+    MatProgressSpinnerModule
   ]
 })
 export class MaterialModule {}
diff --git a/src/app/shared/services/user/user-profile.service.spec.ts b/src/app/shared/services/user/user-profile.service.spec.ts
new file mode 100644
index 0000000000..8c2c2a80cf
--- /dev/null
+++ b/src/app/shared/services/user/user-profile.service.spec.ts
@@ -0,0 +1,154 @@
+import { TestBed } from '@angular/core/testing';
+import { of } from 'rxjs';
+import { Role } from 'shared/enum/role';
+import { ParentService } from '../parent/parent.service';
+import { ProviderService } from '../provider/provider.service';
+import { EmployeeService } from '../employee/employee.service';
+import { MinistryAdminService } from '../ministry-admin/ministry-admin.service';
+import { RegionAdminService } from '../region-admin/region-admin.service';
+import { AreaAdminService } from '../area-admin/area-admin.service';
+import { UserProfileService } from './user-profile.service';
+
+describe('UserProfileService', () => {
+  let service: UserProfileService;
+
+  // Create simple mock objects for all dependencies:
+  const parentServiceMock = {
+    getProfile: jest.fn().mockReturnValue(of({ profile: 'parent profile' }))
+  };
+  const providerServiceMock = {
+    getProfile: jest.fn().mockReturnValue(of({ profile: 'provider profile' }))
+  };
+  const employeeServiceMock = {
+    getEmployeeById: jest.fn().mockReturnValue(of({ profile: 'employee profile' }))
+  };
+  const ministryAdminServiceMock = {
+    getAdminProfile: jest.fn().mockReturnValue(of({ profile: 'ministry admin profile' }))
+  };
+  const regionAdminServiceMock = {
+    getAdminProfile: jest.fn().mockReturnValue(of({ profile: 'region admin profile' }))
+  };
+  const areaAdminServiceMock = {
+    getAdminProfile: jest.fn().mockReturnValue(of({ profile: 'area admin profile' }))
+  };
+
+  beforeEach(() => {
+    TestBed.configureTestingModule({
+      providers: [
+        UserProfileService,
+        { provide: ParentService, useValue: parentServiceMock },
+        { provide: ProviderService, useValue: providerServiceMock },
+        { provide: EmployeeService, useValue: employeeServiceMock },
+        { provide: MinistryAdminService, useValue: ministryAdminServiceMock },
+        { provide: RegionAdminService, useValue: regionAdminServiceMock },
+        { provide: AreaAdminService, useValue: areaAdminServiceMock }
+      ]
+    });
+    service = TestBed.inject(UserProfileService);
+  });
+
+  describe('getStateKeyByRole', () => {
+    it('should return "parent" for Role.parent', () => {
+      expect(service.getStateKeyByRole(Role.parent)).toBe('parent');
+    });
+    it('should return "provider" for Role.provider', () => {
+      expect(service.getStateKeyByRole(Role.provider)).toBe('provider');
+    });
+    it('should return "employee" for Role.employee', () => {
+      expect(service.getStateKeyByRole(Role.employee)).toBe('employee');
+    });
+    it('should return "ministryAdmin" for Role.ministryAdmin', () => {
+      expect(service.getStateKeyByRole(Role.ministryAdmin)).toBe('ministryAdmin');
+    });
+    it('should return "regionAdmin" for Role.regionAdmin', () => {
+      expect(service.getStateKeyByRole(Role.regionAdmin)).toBe('regionAdmin');
+    });
+    it('should return "areaAdmin" for Role.areaAdmin', () => {
+      expect(service.getStateKeyByRole(Role.areaAdmin)).toBe('areaAdmin');
+    });
+    it('should return "all" for Role.all', () => {
+      expect(service.getStateKeyByRole(Role.all)).toBe('all');
+    });
+    it('should return "unauthorized" for Role.unauthorized', () => {
+      expect(service.getStateKeyByRole(Role.unauthorized)).toBe('unauthorized');
+    });
+    it('should return "providerDeputy" for Role.providerDeputy', () => {
+      expect(service.getStateKeyByRole(Role.providerDeputy)).toBe('providerDeputy');
+    });
+    it('should return "techAdmin" for Role.techAdmin', () => {
+      expect(service.getStateKeyByRole(Role.techAdmin)).toBe('techAdmin');
+    });
+    it('should return "moderator" for Role.moderator', () => {
+      expect(service.getStateKeyByRole(Role.moderator)).toBe('moderator');
+    });
+    it('should return an empty string for unknown role', () => {
+      expect(service.getStateKeyByRole(null)).toBe('');
+    });
+  });
+
+  describe('getProfileObservableByRole', () => {
+    it('should return parent profile observable for Role.parent', (done) => {
+      const obs = service.getProfileObservableByRole(Role.parent, '123');
+      expect(obs).not.toBeNull();
+      obs.subscribe((data) => {
+        expect(data.profile).toBe('parent profile');
+        done();
+      });
+    });
+
+    it('should return provider profile observable for Role.provider', (done) => {
+      const obs = service.getProfileObservableByRole(Role.provider, '123');
+      expect(obs).not.toBeNull();
+      obs.subscribe((data) => {
+        expect(data.profile).toBe('provider profile');
+        done();
+      });
+    });
+
+    it('should return employee profile observable for Role.employee', (done) => {
+      const obs = service.getProfileObservableByRole(Role.employee, '123');
+      expect(obs).not.toBeNull();
+      obs.subscribe((data) => {
+        expect(data.profile).toBe('employee profile');
+        done();
+      });
+    });
+
+    it('should return ministry admin profile observable for Role.ministryAdmin', (done) => {
+      const obs = service.getProfileObservableByRole(Role.ministryAdmin, '123');
+      expect(obs).not.toBeNull();
+      obs.subscribe((data) => {
+        expect(data.profile).toBe('ministry admin profile');
+        done();
+      });
+    });
+
+    it('should return region admin profile observable for Role.regionAdmin', (done) => {
+      const obs = service.getProfileObservableByRole(Role.regionAdmin, '123');
+      expect(obs).not.toBeNull();
+      obs.subscribe((data) => {
+        expect(data.profile).toBe('region admin profile');
+        done();
+      });
+    });
+
+    it('should return area admin profile observable for Role.areaAdmin', (done) => {
+      const obs = service.getProfileObservableByRole(Role.areaAdmin, '123');
+      expect(obs).not.toBeNull();
+      obs.subscribe((data) => {
+        expect(data.profile).toBe('area admin profile');
+        done();
+      });
+    });
+
+    // For roles that return new Observable<any>() instances:
+    it('should return a new Observable for Role.all, Role.unauthorized, Role.techAdmin, and Role.moderator', () => {
+      const rolesToTest = [Role.all, Role.unauthorized, Role.techAdmin, Role.moderator];
+      rolesToTest.forEach((role) => {
+        const obs = service.getProfileObservableByRole(role, '123');
+        // Check that we got something truthy; these observables likely don’t emit data.
+        expect(obs).toBeTruthy();
+      });
+    });
+  });
+});
diff --git a/src/app/shared/services/user/user-profile.service.ts b/src/app/shared/services/user/user-profile.service.ts
new file mode 100644
index 0000000000..b34b3580e2
--- /dev/null
+++ b/src/app/shared/services/user/user-profile.service.ts
@@ -0,0 +1,77 @@
+import { Injectable } from '@angular/core';
+import { Role } from 'shared/enum/role';
+import { Observable } from 'rxjs';
+import { Parent } from 'shared/models/parent.model';
+import { Provider } from 'shared/models/provider.model';
+import { Employee } from 'shared/models/employee.model';
+import { MinistryAdmin } from 'shared/models/ministry-admin.model';
+import { RegionAdmin } from 'shared/models/region-admin.model';
+import { AreaAdmin } from 'shared/models/area-admin.model';
+import { ParentService } from '../parent/parent.service';
+import { ProviderService } from '../provider/provider.service';
+import { EmployeeService } from '../employee/employee.service';
+import { MinistryAdminService } from '../ministry-admin/ministry-admin.service';
+import { RegionAdminService } from '../region-admin/region-admin.service';
+import { AreaAdminService } from '../area-admin/area-admin.service';
+
+@Injectable({ providedIn: 'root' })
+export class UserProfileService {
+  constructor(
+    private parentService: ParentService,
+    private providerService: ProviderService,
+    private employeeService: EmployeeService,
+    private ministryAdminService: MinistryAdminService,
+    private regionAdminService: RegionAdminService,
+    private areaAdminService: AreaAdminService
+  ) {}
+
+  /**
+   * Returns the corresponding state key based on the user's role
+   * @param role The user's role
+   * @returns The state key
+   */
+  public getStateKeyByRole(role: Role): string {
+    const roleStateMap: Record<Role, string> = {
+      [Role.parent]: 'parent',
+      [Role.provider]: 'provider',
+      [Role.employee]: 'employee',
+      [Role.ministryAdmin]: 'ministryAdmin',
+      [Role.regionAdmin]: 'regionAdmin',
+      [Role.areaAdmin]: 'areaAdmin',
+      [Role.all]: 'all',
+      [Role.unauthorized]: 'unauthorized',
+      [Role.providerDeputy]: 'providerDeputy',
+      [Role.techAdmin]: 'techAdmin',
+      [Role.moderator]: 'moderator'
+    };
+
+    return roleStateMap[role] || '';
+  }
+
+  /**
+   * Returns the corresponding profile observable based on the user's role
+   * @param role The user's role
+   * @param userId The user's id
+   * @returns The profile observable
+   */
+  public getProfileObservableByRole(
+    role: Role,
+    userId: string
+  ): Observable<Parent | Provider | Employee | MinistryAdmin | RegionAdmin | AreaAdmin> | null {
+    const roleServiceMap: Record<Role, Observable<Parent | Provider | Employee | MinistryAdmin | RegionAdmin | AreaAdmin>> = {
+      [Role.parent]: this.parentService.getProfile(),
+      [Role.provider]: this.providerService.getProfile(),
+      [Role.providerDeputy]: this.employeeService.getEmployeeById(userId),
+      [Role.employee]: this.employeeService.getEmployeeById(userId),
+      [Role.ministryAdmin]: this.ministryAdminService.getAdminProfile(),
+      [Role.regionAdmin]: this.regionAdminService.getAdminProfile(),
+      [Role.areaAdmin]: this.areaAdminService.getAdminProfile(),
+      [Role.all]: new Observable<any>(),
+      [Role.unauthorized]: new Observable<any>(),
+      [Role.techAdmin]: new Observable<any>(),
+      [Role.moderator]: new Observable<any>()
+    };
+
+    return roleServiceMap[role] || null;
+  }
+}
diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts
index 325eb852ee..60b081121e 100644
--- a/src/app/shared/shared.module.ts
+++ b/src/app/shared/shared.module.ts
@@ -86,11 +86,13 @@ import { TextSliceTransformPipe } from './pipes/text-slice-transform.pipe';
 import { TranslateCasesPipe } from './pipes/translate-cases.pipe';
 import { ServerErrorPageComponent } from './components/server-error-page/server-error-page.component';
 import { ThemeSwitcherComponent } from './components/theme-switcher/theme-switcher.component';
-import { TimeRemainingPipe } from './pipes/time-remaining.pipe';
-import { CompetitionCardComponent } from './components/competition-card/competition-card.component';
+import { ProfileWaitingPageComponent } from './components/profile-waiting-page/profile-waiting-page.component';
+import { ForbiddenPageComponent } from './components/forbidden-page/forbidden-page.component';
 import { CreateContactsComponent } from './base-components/create-contacts/create-contacts.component';
 import { WorkshopListComponent } from './components/workshop-list/workshop-list.component';
 import { WorkshopInfoComponent } from './components/workshop-list/workshop-info/workshop-info.component';
+import { TimeRemainingPipe } from './pipes/time-remaining.pipe';
+import { CompetitionCardComponent } from './components/competition-card/competition-card.component';
 
 @NgModule({
   declarations: [
@@ -174,7 +176,9 @@ import { WorkshopInfoComponent } from './components/workshop-list/workshop-info/
     StepperDirective,
     CompetitionCardComponent,
     CreateContactsComponent,
-    CreateAddressFormComponent
+    CreateAddressFormComponent,
+    ProfileWaitingPageComponent,
+    ForbiddenPageComponent
   ],
 
   imports: [
@@ -267,7 +271,8 @@ import { WorkshopInfoComponent } from './components/workshop-list/workshop-info/
     StepperDirective,
     CompetitionCardComponent,
     CreateContactsComponent,
-    CreateAddressFormComponent
+    CreateAddressFormComponent,
+    ProfileWaitingPageComponent
   ]
 })
 export class SharedModule {}
diff --git a/src/app/shared/store/app.actions.ts b/src/app/shared/store/app.actions.ts
index e207c184c5..f7de5688ca 100644
--- a/src/app/shared/store/app.actions.ts
+++ b/src/app/shared/store/app.actions.ts
@@ -1,5 +1,32 @@
+import { AreaAdmin } from 'shared/models/area-admin.model';
+import { Employee } from 'shared/models/employee.model';
 import { MessageBarData } from 'shared/models/message-bar.model';
+import { MinistryAdmin } from 'shared/models/ministry-admin.model';
+import { Parent } from 'shared/models/parent.model';
+import { Provider } from 'shared/models/provider.model';
+import { RegionAdmin } from 'shared/models/region-admin.model';
 import { TimerData } from 'shared/models/server-error';
+import { User } from 'shared/models/user.model';
+
+export class SetProfile {
+  static readonly type = '[app] set profile';
+  constructor(public payload: Parent | Provider | Employee | MinistryAdmin | RegionAdmin | AreaAdmin) {}
+}
+
+export class ClearProfile {
+  static readonly type = '[app] clear profile';
+  constructor() {}
+}
+
+export class SetPersonalInfo {
+  static readonly type = '[app] set personal info';
+  constructor(public payload: User) {}
+}
+
+export class ClearPersonalInfo {
+  static readonly type = '[app] clear personal info';
+  constructor() {}
+}
 
 export class ToggleLoading {
   static readonly type = '[app] toggle page loading';
diff --git a/src/app/shared/store/app.state.ts b/src/app/shared/store/app.state.ts
index b76d6cf7e9..89ca97adf3 100644
--- a/src/app/shared/store/app.state.ts
+++ b/src/app/shared/store/app.state.ts
@@ -4,9 +4,29 @@ import { Action, Selector, State, StateContext } from '@ngxs/store';
 import { MatSnackBar } from '@angular/material/snack-bar';
 import { MessageBarComponent } from 'shared/components/message-bar/message-bar.component';
 import { TimerData } from 'shared/models/server-error';
-import { ActivateEditMode, ClearMessageBar, MarkFormDirty, SetErrorTimerData, ShowMessageBar, ToggleMobileScreen } from './app.actions';
+import { Parent } from 'shared/models/parent.model';
+import { Provider } from 'shared/models/provider.model';
+import { Employee } from 'shared/models/employee.model';
+import { MinistryAdmin } from 'shared/models/ministry-admin.model';
+import { RegionAdmin } from 'shared/models/region-admin.model';
+import { AreaAdmin } from 'shared/models/area-admin.model';
+import { User } from 'shared/models/user.model';
+import {
+  ActivateEditMode,
+  ClearMessageBar,
+  ClearPersonalInfo,
+  ClearProfile,
+  MarkFormDirty,
+  SetErrorTimerData,
+  SetPersonalInfo,
+  SetProfile,
+  ShowMessageBar,
+  ToggleMobileScreen
+} from './app.actions';
 
 export interface AppStateModel {
+  profile: Parent | Provider | Employee | MinistryAdmin | RegionAdmin | AreaAdmin | null;
+  personalInfo: User | null;
   isDirtyForm: boolean;
   isEditMode: boolean;
   isMobileScreen: undefined | boolean;
@@ -16,6 +36,8 @@ export interface AppStateModel {
 @State<AppStateModel>({
   name: 'app',
   defaults: {
+    profile: null,
+    personalInfo: null,
     isDirtyForm: false,
     isEditMode: false,
     isMobileScreen: undefined,
@@ -26,6 +48,16 @@ export interface AppStateModel {
 export class AppState {
   constructor(private snackBar: MatSnackBar) {}
 
+  @Selector()
+  static profile(state: AppStateModel): Parent | Provider | Employee | MinistryAdmin | RegionAdmin | AreaAdmin | null {
+    return state.profile;
+  }
+
+  @Selector()
+  static personalInfo(state: AppStateModel): User | null {
+    return state.personalInfo;
+  }
+
   @Selector()
   static isMobileScreen(state: AppStateModel): boolean {
     return state.isMobileScreen;
@@ -46,6 +78,26 @@ export class AppState {
     return state.timerErrorTime;
   }
 
+  @Action(SetProfile)
+  setProfile({ patchState }: StateContext<AppStateModel>, { payload }: SetProfile): void {
+    patchState({ profile: payload });
+  }
+
+  @Action(ClearProfile)
+  clearProfile({ patchState }: StateContext<AppStateModel>): void {
+    patchState({ profile: null });
+  }
+
+  @Action(SetPersonalInfo)
+  setPersonalInfo({ patchState }: StateContext<AppStateModel>, { payload }: SetPersonalInfo): void {
+    patchState({ personalInfo: payload });
+  }
+
+  @Action(ClearPersonalInfo)
+  clearPersonalInfo({ patchState }: StateContext<AppStateModel>): void {
+    patchState({ personalInfo: null });
+  }
+
   @Action(MarkFormDirty)
   markFormDirty({ patchState }: StateContext<AppStateModel>, { payload }: MarkFormDirty): void {
     patchState({ isDirtyForm: payload });
diff --git a/src/app/shared/store/registration.state.ts b/src/app/shared/store/registration.state.ts
index 6ffdc4c1bc..255c6e3ae7 100644
--- a/src/app/shared/store/registration.state.ts
+++ b/src/app/shared/store/registration.state.ts
@@ -1,11 +1,11 @@
 import { Location } from '@angular/common';
-import { HttpErrorResponse } from '@angular/common/http';
+import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http';
 import { Injectable } from '@angular/core';
 import { Router } from '@angular/router';
-import { Action, Selector, State, StateContext } from '@ngxs/store';
+import { Action, Selector, State, StateContext, Store } from '@ngxs/store';
 import { LoginResponse, OidcSecurityService } from 'angular-auth-oidc-client';
-import { Observable, of } from 'rxjs';
-import { catchError, switchMap, tap } from 'rxjs/operators';
+import { Observable, of, throwError } from 'rxjs';
+import { catchError, finalize, switchMap, tap } from 'rxjs/operators';
 
 import { ModeConstants } from 'shared/constants/constants';
 import { SnackbarText } from 'shared/enum/enumUA/message-bar';
@@ -18,14 +18,9 @@ import { Provider } from 'shared/models/provider.model';
 import { RegionAdmin } from 'shared/models/region-admin.model';
 import { TechAdmin } from 'shared/models/tech-admin.model';
 import { User } from 'shared/models/user.model';
-import { AreaAdminService } from 'shared/services/area-admin/area-admin.service';
-import { EmployeeService } from 'shared/services/employee/employee.service';
-import { MinistryAdminService } from 'shared/services/ministry-admin/ministry-admin.service';
-import { ParentService } from 'shared/services/parent/parent.service';
-import { ProviderService } from 'shared/services/provider/provider.service';
-import { RegionAdminService } from 'shared/services/region-admin/region-admin.service';
 import { UserService } from 'shared/services/user/user.service';
-import { MarkFormDirty, ShowMessageBar } from './app.actions';
+import { UserProfileService } from 'shared/services/user/user-profile.service';
+import { ClearPersonalInfo, ClearProfile, MarkFormDirty, SetPersonalInfo, SetProfile, ShowMessageBar } from './app.actions';
 import {
   CheckAuth,
   CheckRegistration,
@@ -38,6 +33,7 @@ import {
   OnUpdateUserSuccess,
   UpdateUser
 } from './registration.actions';
+import { AppState } from './app.state';
 
 export interface RegistrationStateModel {
   isAuthorized: boolean;
@@ -74,16 +70,12 @@ export interface RegistrationStateModel {
 @Injectable()
 export class RegistrationState {
   constructor(
+    private store: Store,
     private router: Router,
     private location: Location,
     private oidcSecurityService: OidcSecurityService,
     private userService: UserService,
-    private providerService: ProviderService,
-    private employeeService: EmployeeService,
-    private parentService: ParentService,
-    private ministryAdminService: MinistryAdminService,
-    private regionAdminService: RegionAdminService,
-    private areaAdmin: AreaAdminService
+    private userProfileService: UserProfileService
   ) {}
 
   @Selector()
@@ -156,6 +148,8 @@ export class RegistrationState {
         if (auth.isAuthenticated) {
           return dispatch(new GetUserPersonalInfo()).pipe(switchMap(() => dispatch(new CheckRegistration())));
         } else {
+          dispatch(new ClearProfile());
+          dispatch(new ClearPersonalInfo());
           patchState({ role: Role.unauthorized, isAuthorizationLoading: false });
           return of(null);
         }
@@ -174,7 +168,6 @@ export class RegistrationState {
     const state = getState();
     if (state.user.isRegistered) {
       dispatch(new GetProfile());
-      patchState({ isAuthorizationLoading: false });
     } else {
       this.router
         .navigate([state.user.role === Role.parent ? '/create-parent' : '/create-provider', ModeConstants.NEW])
@@ -184,41 +177,59 @@ export class RegistrationState {
 
   @Action(GetProfile)
   getProfile({
+    dispatch,
     patchState,
     getState
-  }: StateContext<RegistrationStateModel>):
-    | Observable<Parent>
-    | Observable<Provider>
-    | Observable<Employee>
-    | Observable<MinistryAdmin>
-    | Observable<RegionAdmin>
-    | Observable<AreaAdmin> {
+  }: StateContext<RegistrationStateModel>): Observable<Parent | Provider | Employee | MinistryAdmin | RegionAdmin | AreaAdmin> {
     const state = getState();
-    patchState({ role: state.user.role as Role });
-
-    switch (state.user.role) {
-      case Role.parent:
-        return this.parentService.getProfile().pipe(tap((parent: Parent) => patchState({ parent })));
-      case Role.provider:
-        return this.providerService.getProfile().pipe(tap((provider: Provider) => patchState({ provider })));
-      case Role.providerDeputy:
-      case Role.employee:
-        // TODO: Add provider patch when profile/provider id will be provided
-        return this.employeeService.getEmployeeById(state.user.id).pipe(tap((employee: Employee) => patchState({ employee })));
-      case Role.ministryAdmin:
-        return this.ministryAdminService.getAdminProfile().pipe(tap((ministryAdmin: MinistryAdmin) => patchState({ ministryAdmin })));
-      case Role.regionAdmin:
-        return this.regionAdminService.getAdminProfile().pipe(tap((regionAdmin: RegionAdmin) => patchState({ regionAdmin })));
-      case Role.areaAdmin:
-        return this.areaAdmin.getAdminProfile().pipe(tap((areaAdmin: AreaAdmin) => patchState({ areaAdmin })));
+    const role = state.user.role as Role;
+    const profileKey = this.userProfileService.getStateKeyByRole(role);
+
+    patchState({ isAuthorizationLoading: true, role });
+
+    const cachedProfile = this.store.selectSnapshot(AppState.profile);
+
+    // if there is cached user profile
+    if (cachedProfile) {
+      patchState({ [profileKey]: cachedProfile, isAuthorizationLoading: false });
+      return;
+    }
+
+    const profileObservable = this.userProfileService.getProfileObservableByRole(role, state.user.id);
+    if (!profileObservable) {
+      patchState({ isAuthorizationLoading: false });
+      return;
     }
+
+    return profileObservable.pipe(
+      tap((profile: Parent | Provider | Employee | MinistryAdmin | RegionAdmin | AreaAdmin) => {
+        dispatch(new SetProfile(profile));
+        patchState({ [profileKey]: profile });
+      }),
+      catchError((error: HttpErrorResponse) => {
+        if (error.status === HttpStatusCode.Unauthorized || error.status === HttpStatusCode.Forbidden) {
+          this.router.navigate(['/forbidden']);
+        }
+        return throwError(() => error);
+      }),
+      finalize(() => patchState({ isAuthorizationLoading: false }))
+    );
   }
 
   @Action(GetUserPersonalInfo)
-  getUserPersonalInfo({ patchState }: StateContext<RegistrationStateModel>): Observable<User> {
+  getUserPersonalInfo({ patchState, dispatch }: StateContext<RegistrationStateModel>): Observable<User> | void {
     patchState({ isLoading: true });
+
+    const cachedPersonalInfo = this.store.selectSnapshot(AppState.personalInfo);
+
+    if (cachedPersonalInfo) {
+      patchState({ user: cachedPersonalInfo, role: cachedPersonalInfo.role as Role, isLoading: false });
+      return;
+    }
+
     return this.userService.getPersonalInfo().pipe(
       tap((user: User) => {
+        dispatch(new SetPersonalInfo(user));
         patchState({ user, role: user.role as Role, isLoading: false });
       })
     );
diff --git a/src/app/shell/details/competition-details/competition-details.component.ts b/src/app/shell/details/competition-details/competition-details.component.ts
index 0a87bd5e22..5e142ca746 100644
--- a/src/app/shell/details/competition-details/competition-details.component.ts
+++ b/src/app/shell/details/competition-details/competition-details.component.ts
@@ -67,7 +67,7 @@ export class CompetitionDetailsComponent implements OnInit {
     this.providerParameters.excludedCompetitionId = this.competition.id;
     this.providerParameters.providerId = this.competition?.organizerOfTheEventId;
     this.getCompetitionData();
-    this.images = this.imageService.getCarouselImages(this.competition);
+    this.images = this.imageService.getCarouselImages(Object.setPrototypeOf(this.competition, Competition.prototype));
   }
 
   public onActionButtonClick(ModalType: ModalConfirmationType): void {
diff --git a/src/app/shell/details/details-tabs/provider-about/provider-about.component.ts b/src/app/shell/details/details-tabs/provider-about/provider-about.component.ts
index 59f63a61fb..e2ba620946 100644
--- a/src/app/shell/details/details-tabs/provider-about/provider-about.component.ts
+++ b/src/app/shell/details/details-tabs/provider-about/provider-about.component.ts
@@ -19,6 +19,6 @@ export class ProviderAboutComponent implements OnInit {
   constructor(private readonly imagesService: ImagesService) {}
 
   public ngOnInit(): void {
-    this.images = this.imagesService.getCarouselImages(this.provider);
+    this.images = this.imagesService.getCarouselImages(Object.setPrototypeOf(this.provider, Provider.prototype));
   }
 }
diff --git a/src/app/shell/shell-routing.module.ts b/src/app/shell/shell-routing.module.ts
index 25a54a6012..4074aff08b 100644
--- a/src/app/shell/shell-routing.module.ts
+++ b/src/app/shell/shell-routing.module.ts
@@ -2,7 +2,7 @@ import { NgModule } from '@angular/core';
 import { RouterModule, Routes } from '@angular/router';
 
 import { ErrorPageComponent } from 'shared/components/error-page/error-page.component';
-import { ServerErrorPageComponent } from 'shared/components/server-error-page/server-error-page.component';
+import { ForbiddenPageComponent } from 'shared/components/forbidden-page/forbidden-page.component';
 import { LoginComponent } from 'shared/components/login/login.component';
 import { NotificationsListComponent } from 'shared/components/notifications/notifications-list/notifications-list.component';
 import { AdminToolsComponent } from './admin-tools/admin-tools.component';
@@ -194,20 +194,9 @@ const routes: Routes = [
     canDeactivate: [CreateGuard],
     canLoad: [CreateParentGuard]
   },
-  { path: 'server-error', component: ServerErrorPageComponent },
   {
-    path: 'create-competition/:param',
-    component: CreateCompetitionComponent,
-    loadChildren: () => import('./personal-cabinet/provider/provider.module').then((m) => m.ProviderModule),
-    canLoad: [ProviderGuard],
-    canDeactivate: [CreateGuard]
-  },
-  {
-    path: 'create-competition/:id/:param',
-    component: CreateCompetitionComponent,
-    loadChildren: () => import('./personal-cabinet/provider/provider.module').then((m) => m.ProviderModule),
-    canLoad: [ProviderGuard],
-    canDeactivate: [CreateGuard]
+    path: 'forbidden',
+    component: ForbiddenPageComponent
   },
   { path: '**', component: ErrorPageComponent }
 ];
diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json
index edf6566788..76aa54c3df 100644
--- a/src/assets/i18n/en.json
+++ b/src/assets/i18n/en.json
@@ -1198,6 +1198,15 @@
     "ERROR_404": "It looks like we can't find the page you're looking for. Error code: 404",
     "ERROR_TRY_MESSAGE": "Please wait for {{time}} seconds before trying again."
   },
+  "WAITING_PAGE": {
+    "WAIT": "Wait",
+    "GETTING_PROFILE": "Retrieving your profile data...",
+    "WAIT_A_MINUTE": "This may take up to a minute."
+  },
+  "FORBIDDEN_PAGE": {
+    "SORRY": "Sorry",
+    "FORBIDDEN": "You are denied account creation"
+  },
   "MORE": "...More",
   "LESS": "...Less",
   "PROVIDER_DRAFT": {
diff --git a/src/assets/i18n/uk.json b/src/assets/i18n/uk.json
index c5b361564d..85a6f145c2 100644
--- a/src/assets/i18n/uk.json
+++ b/src/assets/i18n/uk.json
@@ -1186,6 +1186,15 @@
     "ERROR_404": "Схоже, ми не можемо знайти сторінку, яку Ви шукаєте. Код помилки: 404",
     "ERROR_TRY_MESSAGE": "Ви зможете спробувати ще через {{time}} секунд"
   },
+  "WAITING_PAGE": {
+    "WAIT": "Зачекайте",
+    "GETTING_PROFILE": "Отримуємо дані з вашого профілю...",
+    "WAIT_A_MINUTE": "Це може зайняти до хвилини."
+  },
+  "FORBIDDEN_PAGE": {
+    "SORRY": "Вибачте",
+    "FORBIDDEN": "Вам відмовлено в створенні акаунта"
+  },
   "MORE": "...Більше",
   "LESS": "...Менше",
   "PROVIDER_DRAFT": {


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>♻️ Duplicate comments (1)</summary><blockquote>

<details>
<summary>src/app/shared/store/registration.state.ts (1)</summary><blockquote>

`228-234`: **Duplicate comment: Note the repeated `persolnalInfo` key.**

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (8)</summary><blockquote>

<details>
<summary>src/app/shared/components/profile-waiting-page/profile-waiting-page.component.spec.ts (1)</summary><blockquote>

`1-23`: **Test suite covers basic component creation.**

The test setup properly imports necessary dependencies including TranslateModule which is needed for the translation pipes in the template. The test verifies that the component can be successfully created and rendered.


For a more comprehensive test suite, you might consider adding tests to verify that the content is correctly displayed with the expected translations, but this basic test is sufficient for a simple UI component without complex logic.

</blockquote></details>
<details>
<summary>src/app/shared/components/forbidden-page/forbidden-page.component.scss (2)</summary><blockquote>

`3-14`: **Consider making the container width responsive**

The fixed width of 716px might cause display issues on smaller screens or devices.

```diff
.forbidden-container {
-  width: 716px;
+  max-width: 716px;
+  width: 100%;
  background-color: #ffffff;
  border-radius: 0.5rem;
  padding: 36px 24px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  box-shadow: 0px 6px 16px 0px #00000014;
}
```

---

`16-27`: **Consider using CSS variables for colors**

For better maintainability and consistency, consider using CSS variables for colors instead of hardcoded hex values.

```diff
.forbidden-title {
  font-weight: 800;
  font-size: 24px;
  line-height: 24px;
-  color: #444444;
+  color: var(--text-color, #444444);
}

.forbidden-text {
  font-size: 16px;
  font-weight: 700;
-  color: #444444;
+  color: var(--text-color, #444444);
}
```

</blockquote></details>
<details>
<summary>src/app/shared/components/profile-waiting-page/profile-waiting-page.component.scss (1)</summary><blockquote>

`1-9`: **Consider adding responsive design for the waiting container.**

The fixed width of 716px may cause layout issues on smaller screens. Consider adding media queries to make the container responsive.

```diff
.waiting-container {
-  width: 716px;
+  max-width: 716px;
+  width: 100%;
  background-color: #ffffff;
  border-radius: 8px;
  padding: 36px 24px;
  box-shadow: 0px 6px 16px 0px #00000014;
  gap: 10px;
  margin-top: 100px;
}
```

</blockquote></details>
<details>
<summary>src/app/shared/shared.module.ts (1)</summary><blockquote>

`253-253`: **Consider exporting ForbiddenPageComponent.**

ProfileWaitingPageComponent is exported but ForbiddenPageComponent is not. While this might be intentional if the component is only used through routing, consider exporting it if it needs to be used in templates of components from other modules.

```diff
  ThemeSwitcherComponent,
  ScrollToTopComponent,
  ProfileWaitingPageComponent,
+  ForbiddenPageComponent
```

</blockquote></details>
<details>
<summary>src/app/shared/services/user/user-profile.service.ts (2)</summary><blockquote>

`44-65`: **Consider providing strongly-typed profile observables instead of using `any`.**

Returning `Observable<any>` for multiple roles may reduce type-safety and maintainability. Consider defining explicit interfaces for each profile type or creating a union of specific profile interfaces.

---

`59-61`: **Clarify the behavior for roles returning new empty observables.**

Currently, for `Role.all`, `Role.unauthorized`, and `Role.techAdmin`, this code creates new, never-emitting observables. If these roles are not expected to fetch any data, consider returning `null` or a more explicit `of(null)` to indicate clearly that there is no data.

```diff
...
[Role.all]: of(null),
[Role.unauthorized]: of(null),
[Role.techAdmin]: of(null)
...
```

</blockquote></details>
<details>
<summary>src/app/shared/store/registration.state.ts (1)</summary><blockquote>

`149-150`: **Possible spelling mistake in sessionStorage key.**

It appears `'persolnalInfo'` might be a typo. If so, consider aligning it with more conventional naming (e.g., `'personalInfo'`):

```diff
-  sessionStorage.removeItem('persolnalInfo');
+  sessionStorage.removeItem('personalInfo');
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between a9a19b2aa8635cbcba2455250c72eb0042cc3f63 and d983013b2baeb5fbfbfd4101d36eaafa53c23104.

</details>

<details>
<summary>⛔ Files ignored due to path filters (2)</summary>

* `src/assets/i18n/en.json` is excluded by `!src/assets/**` and included by `src/**`
* `src/assets/i18n/uk.json` is excluded by `!src/assets/**` and included by `src/**`

</details>

<details>
<summary>📒 Files selected for processing (15)</summary>

* `src/app/app.component.html` (1 hunks)
* `src/app/app.component.scss` (0 hunks)
* `src/app/shared/components/forbidden-page/forbidden-page.component.html` (1 hunks)
* `src/app/shared/components/forbidden-page/forbidden-page.component.scss` (1 hunks)
* `src/app/shared/components/forbidden-page/forbidden-page.component.spec.ts` (1 hunks)
* `src/app/shared/components/forbidden-page/forbidden-page.component.ts` (1 hunks)
* `src/app/shared/components/profile-waiting-page/profile-waiting-page.component.html` (1 hunks)
* `src/app/shared/components/profile-waiting-page/profile-waiting-page.component.scss` (1 hunks)
* `src/app/shared/components/profile-waiting-page/profile-waiting-page.component.spec.ts` (1 hunks)
* `src/app/shared/components/profile-waiting-page/profile-waiting-page.component.ts` (1 hunks)
* `src/app/shared/modules/material.module.ts` (2 hunks)
* `src/app/shared/services/user/user-profile.service.ts` (1 hunks)
* `src/app/shared/shared.module.ts` (3 hunks)
* `src/app/shared/store/registration.state.ts` (5 hunks)
* `src/app/shell/shell-routing.module.ts` (2 hunks)

</details>

<details>
<summary>💤 Files with no reviewable changes (1)</summary>

* src/app/app.component.scss

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (3)</summary>

<details>
<summary>src/app/shared/components/profile-waiting-page/profile-waiting-page.component.ts (1)</summary><blockquote>

<details>
<summary>src/app/shared/components/forbidden-page/forbidden-page.component.ts (1)</summary>

* `Component` (5-16)

</details>

</blockquote></details>
<details>
<summary>src/app/shared/components/forbidden-page/forbidden-page.component.spec.ts (1)</summary><blockquote>

<details>
<summary>src/app/shared/store/registration.actions.ts (1)</summary>

* `Logout` (10-13)

</details>

</blockquote></details>
<details>
<summary>src/app/shared/services/user/user-profile.service.ts (1)</summary><blockquote>

<details>
<summary>src/app/shared/store/registration.state.ts (1)</summary>

* `role` (120-122)

</details>

</blockquote></details>

</details><details>
<summary>🪛 Biome (1.9.4)</summary>

<details>
<summary>src/app/shared/store/registration.state.ts</summary>

[error] 188-188: void is confusing inside a union type.

Unsafe fix: Use undefined instead.


(lint/suspicious/noConfusingVoidType)

---

[error] 226-226: void is confusing inside a union type.

Unsafe fix: Use undefined instead.


(lint/suspicious/noConfusingVoidType)

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (2)</summary>

* GitHub Check: build_and_test
* GitHub Check: SonarCloud

</details>

<details>
<summary>🔇 Additional comments (16)</summary><blockquote>

<details>
<summary>src/app/shared/components/profile-waiting-page/profile-waiting-page.component.ts (1)</summary>

`1-8`: **Clean and well-structured component implementation.**

This new component follows Angular best practices with a proper component decorator, selector, and file references. The empty class implementation is appropriate for a simple UI component that doesn't require additional logic.

</details>
<details>
<summary>src/app/shared/components/profile-waiting-page/profile-waiting-page.component.html (1)</summary>

`1-10`: **Well-designed waiting page with clear user feedback.**

The template effectively uses:
- Flexbox for responsive centering and layout
- Translation pipes for internationalization
- Material progress spinner to indicate ongoing activity
- Clear messaging to set user expectations

The hierarchical structure of containers and content is well-organized and follows good UI practices for loading states.

</details>
<details>
<summary>src/app/shared/components/forbidden-page/forbidden-page.component.html (1)</summary>

`1-7`: **Clear and effective forbidden page implementation.**

The template provides:
- A well-structured layout using flexbox
- Clear messaging through translated content
- An accessible action button with appropriate data-testid for testing
- Good user experience for handling access restrictions

This aligns perfectly with the PR objective to create a page for users who are forbidden from accessing their accounts.

</details>
<details>
<summary>src/app/shared/components/forbidden-page/forbidden-page.component.ts (1)</summary>

`1-16`: **Well structured component!**

This component is well-organized, following Angular best practices with proper imports, component decoration, and a clear implementation of the logout functionality when users navigate back from the forbidden page.

A minor suggestion would be to rename the `onBack()` method to `onLogout()` to make its purpose more explicit, but the current implementation is perfectly acceptable.

</details>
<details>
<summary>src/app/shared/modules/material.module.ts (1)</summary>

`30-30`: **LGTM - Material module correctly updated**

The MatProgressSpinnerModule has been properly imported and added to the exports array, following the established pattern in this module.



Also applies to: 62-63

</details>
<details>
<summary>src/app/app.component.html (1)</summary>

`3-15`: **Great implementation of conditional rendering**

The conditional rendering implementation is excellent. Using ng-container to avoid unnecessary DOM elements and the async pipe to handle the observable is the right approach. The structure clearly separates the loading state from the content state.

</details>
<details>
<summary>src/app/shared/components/profile-waiting-page/profile-waiting-page.component.scss (1)</summary>

`27-32`: **LGTM - Appropriate styling for duration text.**

The styling for the waiting duration text is well-defined with appropriate font size, color, and line height for secondary information.

</details>
<details>
<summary>src/app/shell/shell-routing.module.ts (2)</summary>

`5-5`: **LGTM - Properly imported ForbiddenPageComponent.**

The ForbiddenPageComponent is correctly imported from the shared components directory.

---

`198-199`: **LGTM - Correct implementation of forbidden route.**

The forbidden route is properly configured with the ForbiddenPageComponent. This route will handle cases where users are forbidden from accessing their accounts, as mentioned in the PR objectives.

</details>
<details>
<summary>src/app/shared/components/forbidden-page/forbidden-page.component.spec.ts (2)</summary>

`8-12`: **LGTM - Well-structured test setup.**

The test setup follows Angular testing best practices with proper component fixture and mock store declaration.

---

`33-37`: **LGTM - Proper action dispatch test.**

The test correctly verifies that the onBack() method dispatches the Logout action and confirms it's called exactly once.

</details>
<details>
<summary>src/app/shared/shared.module.ts (2)</summary>

`89-90`: **LGTM - Properly imported new components.**

Both ProfileWaitingPageComponent and ForbiddenPageComponent are correctly imported.

---

`166-167`: **LGTM - Components properly declared in the module.**

Both new components are correctly added to the module's declarations array.

</details>
<details>
<summary>src/app/shared/store/registration.state.ts (3)</summary>

`7-8`: **Imports look good.**

---

`22-22`: **UserProfileService import is properly introduced.**

---

`76-76`: **Constructor injection is properly set up.**

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-14T09:52:46Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>🧹 Nitpick comments (4)</summary><blockquote>

<details>
<summary>src/app/shell/details/details-tabs/provider-about/provider-about.component.ts (1)</summary><blockquote>

`22-22`: **Consider using an alternative to `Object.setPrototypeOf`.**

Using `Object.setPrototypeOf` can impact performance as it breaks optimizations in JavaScript engines. Consider creating a new object with the correct prototype instead, especially if dealing with deserialized data.


```diff
-    this.images = this.imagesService.getCarouselImages(Object.setPrototypeOf(this.provider, Provider.prototype));
+    // Create a new object with the Provider prototype
+    const providerWithPrototype = Object.assign(Object.create(Provider.prototype), this.provider);
+    this.images = this.imagesService.getCarouselImages(providerWithPrototype);
```

Alternatively, if changing the `ImagesService` is an option, update it to not rely on prototypes:

```javascript
// In ImagesService
public getCarouselImages(entity: Workshop | Provider | Competition): ImgPath[] {
  if (entity.imageIds?.length) {
    return entity.imageIds.map((imgId: string) => ({ path: environment.storageUrl + imgId }));
  }

  const isWorkshop = 'directionIds' in entity; // Unique property in Workshop
  const isCompetition = 'competitionStatus' in entity; // Unique property in Competition

  return [{
    path: isWorkshop 
      ? 'assets/images/groupimages/workshop-img.png'
      : isCompetition
        ? 'assets/images/groupimages/competition-img.png'
        : null
  }];
}
```

</blockquote></details>
<details>
<summary>src/app/shell/details/details-tabs/workshop-about/workshop-about.component.ts (1)</summary><blockquote>

`30-30`: **Consider using an alternative to `Object.setPrototypeOf`.**

Using `Object.setPrototypeOf` can lead to performance issues as it breaks optimizations in JavaScript engines. Consider creating a new object with the correct prototype instead, especially if dealing with deserialized data.


```diff
-    this.images = this.imagesService.getCarouselImages(Object.setPrototypeOf(this.workshop, Workshop.prototype));
+    // Create a new object with the Workshop prototype
+    const workshopWithPrototype = Object.assign(Object.create(Workshop.prototype), this.workshop);
+    this.images = this.imagesService.getCarouselImages(workshopWithPrototype);
```

</blockquote></details>
<details>
<summary>src/app/shell/details/competition-details/competition-details.component.ts (2)</summary><blockquote>

`69-69`: **Consider using an alternative to `Object.setPrototypeOf`.**

Using `Object.setPrototypeOf` can lead to performance issues as it breaks optimizations in JavaScript engines. Consider creating a new object with the correct prototype instead, especially if dealing with deserialized data.


```diff
-    this.images = this.imageService.getCarouselImages(Object.setPrototypeOf(this.competition, Competition.prototype));
+    // Create a new object with the Competition prototype
+    const competitionWithPrototype = Object.assign(Object.create(Competition.prototype), this.competition);
+    this.images = this.imageService.getCarouselImages(competitionWithPrototype);
```

---

`84-93`: **Consider removing or updating the commented code.**

There's a block of commented code that seems to be waiting for backend functionality. Consider adding a more descriptive TODO with an issue reference or estimated timeline, or remove it if it's not needed.


```diff
-        /*
-         * todo: this code should be return when
-         * backend add functions that archive/publish user Competition
-         */
-        // return this code when added feature for published competition
-        // switchMap(() => {
-        //   if (ModalType === this.ModalType.publishCompetition) {
-        //     return this.store.dispatch(new PublishWorkshop(this.competition.organizerOfTheEventId));
-        //   }
-        // })
+        // TODO: Uncomment when backend implements archive/publish competition functionality
+        // Issue reference: #XXXX
+        // switchMap(() => {
+        //   if (ModalType === this.ModalType.publishCompetition) {
+        //     return this.store.dispatch(new PublishWorkshop(this.competition.organizerOfTheEventId));
+        //   }
+        // })
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between d983013b2baeb5fbfbfd4101d36eaafa53c23104 and 17250ff852384f3ff51e7aa43d0368d81d9e40c1.

</details>

<details>
<summary>📒 Files selected for processing (6)</summary>

* `src/app/shared/services/images/images.service.ts` (1 hunks)
* `src/app/shared/services/user/user-profile.service.ts` (1 hunks)
* `src/app/shared/shared.module.ts` (3 hunks)
* `src/app/shell/details/competition-details/competition-details.component.ts` (2 hunks)
* `src/app/shell/details/details-tabs/provider-about/provider-about.component.ts` (1 hunks)
* `src/app/shell/details/details-tabs/workshop-about/workshop-about.component.ts` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (2)</summary>

* src/app/shared/shared.module.ts
* src/app/shared/services/user/user-profile.service.ts

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (3)</summary>

<details>
<summary>src/app/shell/details/details-tabs/workshop-about/workshop-about.component.ts (1)</summary><blockquote>

<details>
<summary>src/app/shared/models/workshop.model.ts (1)</summary>

* `Workshop` (100-137)

</details>

</blockquote></details>
<details>
<summary>src/app/shell/details/competition-details/competition-details.component.ts (1)</summary><blockquote>

<details>
<summary>src/app/shared/models/competition.model.ts (1)</summary>

* `Competition` (126-153)

</details>

</blockquote></details>
<details>
<summary>src/app/shell/details/details-tabs/provider-about/provider-about.component.ts (1)</summary><blockquote>

<details>
<summary>src/app/shared/models/provider.model.ts (1)</summary>

* `Provider` (106-141)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (2)</summary>

* GitHub Check: SonarCloud
* GitHub Check: build_and_test

</details>

<details>
<summary>🔇 Additional comments (2)</summary><blockquote>

<details>
<summary>src/app/shared/services/images/images.service.ts (1)</summary>

`35-35`: <details>
<summary>❓ Verification inconclusive</summary>

**The TODO should be addressed before shipping to production.**

There's a TODO comment indicating that the competition image needs to be added.

---

🏁 Script executed:

```shell
#!/bin/bash
# Description: Check if there's already a competition image in the assets directory
fd "competition.*\.png" assets/images/
```

Length of output: 142

---



</details>

Below is a new verification script to check for the competition image in the expected folder. Please run this script manually to verify that the file exists in the correct directory, as the previous attempt failed due to searching in an incorrect path:

```shell
#!/bin/bash
# Verify if the competition image exists in the correct directory (assets/images/groupimages)
if [ -d "assets/images/groupimages" ]; then
    echo "Directory assets/images/groupimages exists."
    fd "competition.*\.png" assets/images/groupimages
else
    echo "Directory assets/images/groupimages does not exist."
fi
```

---

**Action Required: Verify Competition Image Asset**

• The TODO comment in `src/app/shared/services/images/images.service.ts` (line 35) highlights that a competition image is missing from production.  
• Please verify that `competition-img.png` (or any matching file) exists in the directory `assets/images/groupimages`.  
• If the asset is absent, add the appropriate competition image or update the code’s path if the asset is stored elsewhere.  
• Remove the TODO comment once the issue is resolved.

</details>
<details>
<summary>src/app/shell/details/competition-details/competition-details.component.ts (1)</summary>

`6-6`: **Dependency cleanup looks good.**

Removing the unused `switchMap` operator helps keep imports clean.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-15T07:32:50Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (2)</summary><blockquote>

<details>
<summary>src/app/shared/services/user/user-profile.service.spec.ts (2)</summary><blockquote>

`89-143`: **Consider adding a test for Role.providerDeputy**

While the `getStateKeyByRole` method is tested for Role.providerDeputy (lines 75-77), there's no corresponding test for this role in the `getProfileObservableByRole` tests.

```diff
+    it('should return provider deputy profile observable for Role.providerDeputy', (done) => {
+      const obs = service.getProfileObservableByRole(Role.providerDeputy, '123');
+      expect(obs).not.toBeNull();
+      obs.subscribe((data) => {
+        // Update expected profile based on implementation
+        expect(data.profile).toBe('provider profile');
+        done();
+      });
+    });
+
```

---

`144-153`: **Consider enhancing tests for special roles**

The test for special roles (all, unauthorized, techAdmin, moderator) only verifies that the observable is truthy, but doesn't verify what data it emits (if any). If these roles are expected to emit specific data in the actual implementation, consider adding assertions to verify that behavior.


If these observables are expected to emit data, you could expand the test to verify the emissions:

```diff
    it('should return a new Observable for Role.all, Role.unauthorized, Role.techAdmin, and Role.moderator', () => {
      const rolesToTest = [Role.all, Role.unauthorized, Role.techAdmin, Role.moderator];
      rolesToTest.forEach((role) => {
        const obs = service.getProfileObservableByRole(role, '123');
        // Check that we got something truthy; these observables likely don't emit data.
        expect(obs).toBeTruthy();
+        // If these observables do emit data, you could test the emissions:
+        // obs.subscribe(data => {
+        //   expect(data).toBeDefined();
+        //   // Add assertions based on expected data
+        // });
      });
    });
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 17250ff852384f3ff51e7aa43d0368d81d9e40c1 and 42cb2df84a519c0fc3c5f4efb47f1132c41889ca.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `src/app/shared/components/forbidden-page/forbidden-page.component.ts` (1 hunks)
* `src/app/shared/services/user/user-profile.service.spec.ts` (1 hunks)

</details>

<details>
<summary>✅ Files skipped from review due to trivial changes (1)</summary>

* src/app/shared/components/forbidden-page/forbidden-page.component.ts

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (2)</summary>

* GitHub Check: build_and_test
* GitHub Check: SonarCloud

</details>

<details>
<summary>🔇 Additional comments (2)</summary><blockquote>

<details>
<summary>src/app/shared/services/user/user-profile.service.spec.ts (2)</summary>

`1-48`: **Well-structured test setup with comprehensive service mocks**

The test setup is well-organized with clear mock implementations for all dependent services. The use of Jest mock functions with `mockReturnValue` is an effective approach for testing observables.

---

`50-87`: **Good coverage of all role types in getStateKeyByRole tests**

The tests thoroughly verify the mapping between Role enum values and their corresponding state keys, including edge cases like unknown/null roles. This exhaustive approach ensures the role-based routing logic is well-tested.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-15T07:59:00Z
user: login: humanmantis
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-23T14:17:34Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-23T14:18:07Z
user: login: YakymivY
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T17:26:34Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-25T17:26:39Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 3**

<details>
<summary>🔭 Outside diff range comments (1)</summary><blockquote>

<details>
<summary>src/app/shared/store/app.state.ts (1)</summary><blockquote>

`127-130`: _⚠️ Potential issue_

**Wrong action type used in handler signature**

`ToggleMobileScreen` handler currently destructures its second argument as `ActivateEditMode`, which masks the real payload type and defeats type-checking.

```diff
-@Action(ToggleMobileScreen)
-ToggleMobileScreen({ patchState }: StateContext<AppStateModel>, { payload }: ActivateEditMode): void {
+@Action(ToggleMobileScreen)
+ToggleMobileScreen({ patchState }: StateContext<AppStateModel>, { payload }: ToggleMobileScreen): void {
   patchState({ isMobileScreen: payload });
 }
```

</blockquote></details>

</blockquote></details>

<details>
<summary>♻️ Duplicate comments (1)</summary><blockquote>

<details>
<summary>src/app/shared/store/registration.state.ts (1)</summary><blockquote>

`220-229`: **Repeat of “void in a union” issue – favour `undefined`**

Static analysis still flags the `void` part:

```diff
-getUserPersonalInfo({ patchState, dispatch }: StateContext<RegistrationStateModel>): Observable<User> | void {
+getUserPersonalInfo({ patchState, dispatch }: StateContext<RegistrationStateModel>): Observable<User> | undefined {
```

…and the two early returns should explicitly return `undefined` for clarity:

```diff
-  return;
+  return undefined;
```

This aligns with earlier review feedback and removes Biome’s `noConfusingVoidType` warning.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 221-221: void is confusing inside a union type.

Unsafe fix: Use undefined instead.


(lint/suspicious/noConfusingVoidType)

</details>

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>src/app/shell/shell-routing.module.ts (1)</summary><blockquote>

`198-200`: **Consider guarding the Forbidden route**  

Route `/forbidden` is publicly accessible.  If the intention is to show it **only** after a 401/403 coming from the backend, consider attaching a lightweight guard (or `canActivateChild` on a parent) that prevents direct navigation by authenticated users with valid permissions.  
This avoids accidental exposure of a “forbidden” screen through a mistyped URL and keeps routing semantics clearer.

Example:

```diff
-{
-  path: 'forbidden',
-  component: ForbiddenPageComponent
-},
+{
+  path: 'forbidden',
+  component: ForbiddenPageComponent,
+  canActivate: [ForbiddenGuard]   // simple guard that checks store.errorStatus === 403
+},
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 42cb2df84a519c0fc3c5f4efb47f1132c41889ca and 83921ee5541aa749a866c871cee1512a41ec6718.

</details>

<details>
<summary>⛔ Files ignored due to path filters (2)</summary>

* `src/assets/i18n/en.json` is excluded by `!src/assets/**` and included by `src/**`
* `src/assets/i18n/uk.json` is excluded by `!src/assets/**` and included by `src/**`

</details>

<details>
<summary>📒 Files selected for processing (20)</summary>

* `src/app/app.component.html` (1 hunks)
* `src/app/app.component.scss` (0 hunks)
* `src/app/shared/components/forbidden-page/forbidden-page.component.html` (1 hunks)
* `src/app/shared/components/forbidden-page/forbidden-page.component.scss` (1 hunks)
* `src/app/shared/components/forbidden-page/forbidden-page.component.spec.ts` (1 hunks)
* `src/app/shared/components/forbidden-page/forbidden-page.component.ts` (1 hunks)
* `src/app/shared/components/profile-waiting-page/profile-waiting-page.component.html` (1 hunks)
* `src/app/shared/components/profile-waiting-page/profile-waiting-page.component.scss` (1 hunks)
* `src/app/shared/components/profile-waiting-page/profile-waiting-page.component.spec.ts` (1 hunks)
* `src/app/shared/components/profile-waiting-page/profile-waiting-page.component.ts` (1 hunks)
* `src/app/shared/modules/material.module.ts` (2 hunks)
* `src/app/shared/services/user/user-profile.service.spec.ts` (1 hunks)
* `src/app/shared/services/user/user-profile.service.ts` (1 hunks)
* `src/app/shared/shared.module.ts` (3 hunks)
* `src/app/shared/store/app.actions.ts` (1 hunks)
* `src/app/shared/store/app.state.ts` (4 hunks)
* `src/app/shared/store/registration.state.ts` (6 hunks)
* `src/app/shell/details/competition-details/competition-details.component.ts` (1 hunks)
* `src/app/shell/details/details-tabs/provider-about/provider-about.component.ts` (1 hunks)
* `src/app/shell/shell-routing.module.ts` (2 hunks)

</details>

<details>
<summary>💤 Files with no reviewable changes (1)</summary>

* src/app/app.component.scss

</details>

<details>
<summary>✅ Files skipped from review due to trivial changes (1)</summary>

* src/app/shell/details/competition-details/competition-details.component.ts

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (14)</summary>

* src/app/shell/details/details-tabs/provider-about/provider-about.component.ts
* src/app/shared/components/profile-waiting-page/profile-waiting-page.component.scss
* src/app/shared/components/profile-waiting-page/profile-waiting-page.component.ts
* src/app/shared/components/profile-waiting-page/profile-waiting-page.component.html
* src/app/shared/components/forbidden-page/forbidden-page.component.html
* src/app/shared/components/profile-waiting-page/profile-waiting-page.component.spec.ts
* src/app/shared/modules/material.module.ts
* src/app/app.component.html
* src/app/shared/shared.module.ts
* src/app/shared/components/forbidden-page/forbidden-page.component.ts
* src/app/shared/components/forbidden-page/forbidden-page.component.scss
* src/app/shared/components/forbidden-page/forbidden-page.component.spec.ts
* src/app/shared/services/user/user-profile.service.spec.ts
* src/app/shared/services/user/user-profile.service.ts

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (3)</summary>

<details>
<summary>src/app/shared/store/app.actions.ts (6)</summary><blockquote>

<details>
<summary>src/app/shared/models/parent.model.ts (1)</summary>

* `Parent` (3-11)

</details>
<details>
<summary>src/app/shared/models/provider.model.ts (1)</summary>

* `Provider` (87-115)

</details>
<details>
<summary>src/app/shared/models/ministry-admin.model.ts (1)</summary>

* `MinistryAdmin` (3-3)

</details>
<details>
<summary>src/app/shared/models/region-admin.model.ts (1)</summary>

* `RegionAdmin` (3-11)

</details>
<details>
<summary>src/app/shared/models/area-admin.model.ts (1)</summary>

* `AreaAdmin` (3-14)

</details>
<details>
<summary>src/app/shared/models/user.model.ts (1)</summary>

* `User` (7-33)

</details>

</blockquote></details>
<details>
<summary>src/app/shared/store/registration.state.ts (9)</summary><blockquote>

<details>
<summary>src/app/shared/store/app.actions.ts (4)</summary>

* `ClearProfile` (16-19)
* `ClearPersonalInfo` (26-29)
* `SetProfile` (11-14)
* `SetPersonalInfo` (21-24)

</details>
<details>
<summary>src/app/shared/models/parent.model.ts (1)</summary>

* `Parent` (3-11)

</details>
<details>
<summary>src/app/shared/models/provider.model.ts (1)</summary>

* `Provider` (87-115)

</details>
<details>
<summary>src/app/shared/models/ministry-admin.model.ts (1)</summary>

* `MinistryAdmin` (3-3)

</details>
<details>
<summary>src/app/shared/models/region-admin.model.ts (1)</summary>

* `RegionAdmin` (3-11)

</details>
<details>
<summary>src/app/shared/models/area-admin.model.ts (1)</summary>

* `AreaAdmin` (3-14)

</details>
<details>
<summary>src/app/shared/store/app.state.ts (1)</summary>

* `profile` (52-54)

</details>
<details>
<summary>src/app/shared/store/registration.actions.ts (1)</summary>

* `GetUserPersonalInfo` (55-58)

</details>
<details>
<summary>src/app/shared/models/user.model.ts (1)</summary>

* `User` (7-33)

</details>

</blockquote></details>
<details>
<summary>src/app/shared/store/app.state.ts (7)</summary><blockquote>

<details>
<summary>src/app/shared/models/parent.model.ts (1)</summary>

* `Parent` (3-11)

</details>
<details>
<summary>src/app/shared/models/provider.model.ts (1)</summary>

* `Provider` (87-115)

</details>
<details>
<summary>src/app/shared/models/ministry-admin.model.ts (1)</summary>

* `MinistryAdmin` (3-3)

</details>
<details>
<summary>src/app/shared/models/region-admin.model.ts (1)</summary>

* `RegionAdmin` (3-11)

</details>
<details>
<summary>src/app/shared/models/area-admin.model.ts (1)</summary>

* `AreaAdmin` (3-14)

</details>
<details>
<summary>src/app/shared/models/user.model.ts (1)</summary>

* `User` (7-33)

</details>
<details>
<summary>src/app/shared/store/app.actions.ts (4)</summary>

* `SetProfile` (11-14)
* `ClearProfile` (16-19)
* `SetPersonalInfo` (21-24)
* `ClearPersonalInfo` (26-29)

</details>

</blockquote></details>

</details><details>
<summary>🪛 Biome (1.9.4)</summary>

<details>
<summary>src/app/shared/store/app.actions.ts</summary>

[error] 18-18: This constructor is unnecessary.

Unsafe fix: Remove the unnecessary constructor.


(lint/complexity/noUselessConstructor)

---

[error] 28-28: This constructor is unnecessary.

Unsafe fix: Remove the unnecessary constructor.


(lint/complexity/noUselessConstructor)

</details>
<details>
<summary>src/app/shared/store/registration.state.ts</summary>

[error] 221-221: void is confusing inside a union type.

Unsafe fix: Use undefined instead.


(lint/suspicious/noConfusingVoidType)

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (2)</summary>

* GitHub Check: build_and_test
* GitHub Check: SonarCloud

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-30T06:12:16Z
user: login: humanmantis
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-03T08:16:59Z
user: login: humanmantis
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T17:40:49Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>♻️ Duplicate comments (5)</summary><blockquote>

<details>
<summary>src/app/shared/store/app.actions.ts (3)</summary><blockquote>

`11-14`: **TechAdmin profile type is missing in the union type**

The `SetProfile` payload type omits `TechAdmin`, yet `RegistrationState` still contains that role in its model. Dispatching `SetProfile(new TechAdmin(...))` will break type-safety.


```shell
#!/bin/bash
# Verify if TechAdmin is used as a profile type in the codebase
rg -A 1 "new SetProfile\(" --type ts
```

---

`16-19`: **Remove useless empty constructor to reduce bundle size**

The empty constructor for `ClearProfile` is unnecessary as it matches the default constructor behavior. Static analysis correctly flags this as an issue.


```diff
export class ClearProfile {
  static readonly type = '[app] clear profile';
-  constructor() {}
}
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 18-18: This constructor is unnecessary.

Unsafe fix: Remove the unnecessary constructor.


(lint/complexity/noUselessConstructor)

</details>

</details>

---

`26-29`: **Remove useless empty constructor to reduce bundle size**

Similarly, the empty constructor for `ClearPersonalInfo` is unnecessary and should be removed.


```diff
export class ClearPersonalInfo {
  static readonly type = '[app] clear personal info';
-  constructor() {}
}
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 28-28: This constructor is unnecessary.

Unsafe fix: Remove the unnecessary constructor.


(lint/complexity/noUselessConstructor)

</details>

</details>

</blockquote></details>
<details>
<summary>src/app/shared/store/registration.state.ts (2)</summary><blockquote>

`220-228`: **Replace `void` with `undefined` in union return type**

Using `void` in a union return type can be confusing. Consider using `undefined` for clarity.


```diff
-  getUserPersonalInfo({ patchState, dispatch }: StateContext<RegistrationStateModel>): Observable<User> | void {
+  getUserPersonalInfo({ patchState, dispatch }: StateContext<RegistrationStateModel>): Observable<User> | undefined {
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 221-221: void is confusing inside a union type.

Unsafe fix: Use undefined instead.


(lint/suspicious/noConfusingVoidType)

</details>

</details>

---

`179-217`: _⚠️ Potential issue_

**Fix return type to match early returns**

The `getProfile` method has early `return;` statements without values, which breaks the declared return type `Observable<...>`. This creates misleading method signatures for callers.


```diff
-  }: StateContext<RegistrationStateModel>): Observable<Parent | Provider | Employee | MinistryAdmin | RegionAdmin | AreaAdmin> {
+  }: StateContext<RegistrationStateModel>): Observable<Parent | Provider | Employee | MinistryAdmin | RegionAdmin | AreaAdmin> | undefined {
```

Alternatively, you could use `EMPTY` from RxJS to keep the observable contract:

```diff
+import { EMPTY, Observable, of, throwError } from 'rxjs';
...
-      return;
+      return EMPTY;
...
-      return;
+      return EMPTY;
```

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>src/app/shared/store/registration.state.ts (1)</summary><blockquote>

`210-214`: **Improve error handling for better debugging**

The current implementation navigates to the `/forbidden` page but doesn't provide detailed error information for debugging. Consider logging the error details or implementing a more informative error page.


```diff
catchError((error: HttpErrorResponse) => {
  if (error.status === HttpStatusCode.Unauthorized || error.status === HttpStatusCode.Forbidden) {
    this.router.navigate(['/forbidden']);
+    // Optionally log the error details for debugging
+    console.error('Authentication error:', error);
  }
  return throwError(() => error);
})
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 24d97a46f36e2c3aa50703711bd3bfb30c32069f and 1e531f0f0e6f027f4c543d93abe41ec4e3294f60.

</details>

<details>
<summary>⛔ Files ignored due to path filters (2)</summary>

* `src/assets/i18n/en.json` is excluded by `!src/assets/**` and included by `src/**`
* `src/assets/i18n/uk.json` is excluded by `!src/assets/**` and included by `src/**`

</details>

<details>
<summary>📒 Files selected for processing (20)</summary>

* `src/app/app.component.html` (1 hunks)
* `src/app/app.component.scss` (0 hunks)
* `src/app/shared/components/forbidden-page/forbidden-page.component.html` (1 hunks)
* `src/app/shared/components/forbidden-page/forbidden-page.component.scss` (1 hunks)
* `src/app/shared/components/forbidden-page/forbidden-page.component.spec.ts` (1 hunks)
* `src/app/shared/components/forbidden-page/forbidden-page.component.ts` (1 hunks)
* `src/app/shared/components/profile-waiting-page/profile-waiting-page.component.html` (1 hunks)
* `src/app/shared/components/profile-waiting-page/profile-waiting-page.component.scss` (1 hunks)
* `src/app/shared/components/profile-waiting-page/profile-waiting-page.component.spec.ts` (1 hunks)
* `src/app/shared/components/profile-waiting-page/profile-waiting-page.component.ts` (1 hunks)
* `src/app/shared/modules/material.module.ts` (2 hunks)
* `src/app/shared/services/user/user-profile.service.spec.ts` (1 hunks)
* `src/app/shared/services/user/user-profile.service.ts` (1 hunks)
* `src/app/shared/shared.module.ts` (3 hunks)
* `src/app/shared/store/app.actions.ts` (1 hunks)
* `src/app/shared/store/app.state.ts` (4 hunks)
* `src/app/shared/store/registration.state.ts` (6 hunks)
* `src/app/shell/details/competition-details/competition-details.component.ts` (1 hunks)
* `src/app/shell/details/details-tabs/provider-about/provider-about.component.ts` (1 hunks)
* `src/app/shell/shell-routing.module.ts` (2 hunks)

</details>

<details>
<summary>💤 Files with no reviewable changes (1)</summary>

* src/app/app.component.scss

</details>

<details>
<summary>✅ Files skipped from review due to trivial changes (1)</summary>

* src/app/shared/components/forbidden-page/forbidden-page.component.ts

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (16)</summary>

* src/app/shared/components/profile-waiting-page/profile-waiting-page.component.spec.ts
* src/app/shell/details/competition-details/competition-details.component.ts
* src/app/shell/details/details-tabs/provider-about/provider-about.component.ts
* src/app/shared/components/profile-waiting-page/profile-waiting-page.component.html
* src/app/shared/components/forbidden-page/forbidden-page.component.html
* src/app/shell/shell-routing.module.ts
* src/app/shared/components/profile-waiting-page/profile-waiting-page.component.ts
* src/app/shared/modules/material.module.ts
* src/app/shared/components/profile-waiting-page/profile-waiting-page.component.scss
* src/app/shared/components/forbidden-page/forbidden-page.component.scss
* src/app/app.component.html
* src/app/shared/components/forbidden-page/forbidden-page.component.spec.ts
* src/app/shared/shared.module.ts
* src/app/shared/services/user/user-profile.service.ts
* src/app/shared/services/user/user-profile.service.spec.ts
* src/app/shared/store/app.state.ts

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (1)</summary>

<details>
<summary>src/app/shared/store/app.actions.ts (6)</summary><blockquote>

<details>
<summary>src/app/shared/models/parent.model.ts (1)</summary>

* `Parent` (3-11)

</details>
<details>
<summary>src/app/shared/models/provider.model.ts (1)</summary>

* `Provider` (89-117)

</details>
<details>
<summary>src/app/shared/models/ministry-admin.model.ts (1)</summary>

* `MinistryAdmin` (3-3)

</details>
<details>
<summary>src/app/shared/models/region-admin.model.ts (1)</summary>

* `RegionAdmin` (3-11)

</details>
<details>
<summary>src/app/shared/models/area-admin.model.ts (1)</summary>

* `AreaAdmin` (3-14)

</details>
<details>
<summary>src/app/shared/models/user.model.ts (1)</summary>

* `User` (7-33)

</details>

</blockquote></details>

</details><details>
<summary>🪛 Biome (1.9.4)</summary>

<details>
<summary>src/app/shared/store/app.actions.ts</summary>

[error] 18-18: This constructor is unnecessary.

Unsafe fix: Remove the unnecessary constructor.


(lint/complexity/noUselessConstructor)

---

[error] 28-28: This constructor is unnecessary.

Unsafe fix: Remove the unnecessary constructor.


(lint/complexity/noUselessConstructor)

</details>
<details>
<summary>src/app/shared/store/registration.state.ts</summary>

[error] 221-221: void is confusing inside a union type.

Unsafe fix: Use undefined instead.


(lint/suspicious/noConfusingVoidType)

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (2)</summary>

* GitHub Check: SonarCloud
* GitHub Check: build_and_test

</details>

<details>
<summary>🔇 Additional comments (3)</summary><blockquote>

<details>
<summary>src/app/shared/store/registration.state.ts (3)</summary>

`143-158`: **Looks good: Improved authorization state handling**

The cleanup of profile and personal info on failed authentication is a good practice, ensuring stale data isn't retained between sessions.

---

`190-196`: **Caching implementation looks good**

The check for a cached profile and early return improves performance by avoiding redundant API calls. This is a good pattern.

---

`223-228`: **Personal info caching implementation looks good**

Similar to the profile caching, checking for cached personal info before making an API call is an efficient approach.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-13T07:24:29Z

Review comments:
diff_hunk: @@ -192,33 +183,59 @@ export class RegistrationState {
     | Observable<Employee>
     | Observable<MinistryAdmin>
     | Observable<RegionAdmin>
-    | Observable<AreaAdmin> {
+    | Observable<AreaAdmin>
+    | void {
     const state = getState();
-    patchState({ role: state.user.role as Role });
-
-    switch (state.user.role) {
-      case Role.parent:
-        return this.parentService.getProfile().pipe(tap((parent: Parent) => patchState({ parent })));
-      case Role.provider:
-        return this.providerService.getProfile().pipe(tap((provider: Provider) => patchState({ provider })));
-      case Role.providerDeputy:
-      case Role.employee:
-        // TODO: Add provider patch when profile/provider id will be provided
-        return this.employeeService.getEmployeeById(state.user.id).pipe(tap((employee: Employee) => patchState({ employee })));
-      case Role.ministryAdmin:
-        return this.ministryAdminService.getAdminProfile().pipe(tap((ministryAdmin: MinistryAdmin) => patchState({ ministryAdmin })));
-      case Role.regionAdmin:
-        return this.regionAdminService.getAdminProfile().pipe(tap((regionAdmin: RegionAdmin) => patchState({ regionAdmin })));
-      case Role.areaAdmin:
-        return this.areaAdmin.getAdminProfile().pipe(tap((areaAdmin: AreaAdmin) => patchState({ areaAdmin })));
+    const role = state.user.role as Role;
+    const profileKey = this.userProfileService.getStateKeyByRole(role);
+
+    patchState({ isAuthorizationLoading: true, role });
+
+    const cachedProfile = sessionStorage.getItem('profile');
+
+    // if there is cached user profile
+    if (cachedProfile) {
+      const parsedProfile = JSON.parse(cachedProfile);
+      patchState({ [profileKey]: parsedProfile, isAuthorizationLoading: false });
+      return;
+    }
+
+    const profileObservable = this.userProfileService.getProfileObservableByRole(role, state.user.id);
+    if (!profileObservable) {
+      patchState({ isAuthorizationLoading: false });
+      return;
     }
+
+    return profileObservable.pipe(
+      tap((profile) => {
+        sessionStorage.setItem('profile', JSON.stringify(profile));
+        patchState({ [profileKey]: profile });
+      }),
+      catchError((error: HttpErrorResponse) => {
+        if (error.status === 401 || error.status === 403) {
+          this.router.navigate(['/forbidden']);
+        }
+        return throwError(() => new Error('Something went wrong!'));
+      }),
+      finalize(() => patchState({ isAuthorizationLoading: false }))
+    );
   }
 
   @Action(GetUserPersonalInfo)
-  getUserPersonalInfo({ patchState }: StateContext<RegistrationStateModel>): Observable<User> {
+  getUserPersonalInfo({ patchState }: StateContext<RegistrationStateModel>): Observable<User> | void {
     patchState({ isLoading: true });
path: src/app/shared/store/registration.state.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Replace `void` with `undefined` to satisfy lint rules.**

Similarly, returning `Observable<User> | void` can be confusing. Use `undefined` or refactor to make the return type more straightforward:

```diff
-  getUserPersonalInfo({ patchState }: StateContext<RegistrationStateModel>): Observable<User> | void {
+  getUserPersonalInfo({ patchState }: StateContext<RegistrationStateModel>): Observable<User> | undefined {
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
  getUserPersonalInfo({ patchState }: StateContext<RegistrationStateModel>): Observable<User> | undefined {
    patchState({ isLoading: true });
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 226-226: void is confusing inside a union type.

Unsafe fix: Use undefined instead.


(lint/suspicious/noConfusingVoidType)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-14T09:52:45Z
author_association: NONE
diff_hunk: @@ -192,33 +183,59 @@
     | Observable<Employee>
     | Observable<MinistryAdmin>
     | Observable<RegionAdmin>
-    | Observable<AreaAdmin> {
+    | Observable<AreaAdmin>
+    | void {
     const state = getState();
path: src/app/shared/store/registration.state.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Avoid using `void` in union return types.**

Static analysis warns that `void` in a union type can be confusing. Consider using `undefined` or removing the union type to enhance clarity.

```diff
-    | void {
+    | undefined {
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    | undefined {
    const state = getState();
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 188-188: void is confusing inside a union type.

Unsafe fix: Use undefined instead.


(lint/suspicious/noConfusingVoidType)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-14T09:52:46Z
author_association: NONE
diff_hunk: @@ -28,7 +28,12 @@ export class ImagesService {
 
     return [
       {
-        path: entity instanceof Workshop ? 'assets/images/groupimages/workshop-img.png' : 'assets/images/groupimages/competition-img.png'
+        path:
+          entity.constructor.name === 'Workshop'
+            ? 'assets/images/groupimages/workshop-img.png'
+            : entity.constructor.name === 'Competition'
+              ? 'assets/images/groupimages/competition-img.png' // TODO: add competition image
+              : null
path: src/app/shared/services/images/images.service.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Consider a more robust type checking approach.**

The change from `instanceof` to `constructor.name` string comparison makes the code more vulnerable to minification or transpilation issues in production builds. String-based type checking is generally more fragile than `instanceof` operations.


```diff
-        path:
-          entity.constructor.name === 'Workshop'
-            ? 'assets/images/groupimages/workshop-img.png'
-            : entity.constructor.name === 'Competition'
-              ? 'assets/images/groupimages/competition-img.png' // TODO: add competition image
-              : null
+        path:
+          entity instanceof Workshop
+            ? 'assets/images/groupimages/workshop-img.png'
+            : entity instanceof Competition
+              ? 'assets/images/groupimages/competition-img.png' // TODO: add competition image
+              : null
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
        path:
          entity instanceof Workshop
            ? 'assets/images/groupimages/workshop-img.png'
            : entity instanceof Competition
              ? 'assets/images/groupimages/competition-img.png' // TODO: add competition image
              : null
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-04-15T07:32:49Z
author_association: NONE
diff_hunk: @@ -192,33 +183,59 @@ export class RegistrationState {
     | Observable<Employee>
     | Observable<MinistryAdmin>
     | Observable<RegionAdmin>
-    | Observable<AreaAdmin> {
+    | Observable<AreaAdmin>
+    | void {
     const state = getState();
-    patchState({ role: state.user.role as Role });
-
-    switch (state.user.role) {
-      case Role.parent:
-        return this.parentService.getProfile().pipe(tap((parent: Parent) => patchState({ parent })));
-      case Role.provider:
-        return this.providerService.getProfile().pipe(tap((provider: Provider) => patchState({ provider })));
-      case Role.providerDeputy:
-      case Role.employee:
-        // TODO: Add provider patch when profile/provider id will be provided
-        return this.employeeService.getEmployeeById(state.user.id).pipe(tap((employee: Employee) => patchState({ employee })));
-      case Role.ministryAdmin:
-        return this.ministryAdminService.getAdminProfile().pipe(tap((ministryAdmin: MinistryAdmin) => patchState({ ministryAdmin })));
-      case Role.regionAdmin:
-        return this.regionAdminService.getAdminProfile().pipe(tap((regionAdmin: RegionAdmin) => patchState({ regionAdmin })));
-      case Role.areaAdmin:
-        return this.areaAdmin.getAdminProfile().pipe(tap((areaAdmin: AreaAdmin) => patchState({ areaAdmin })));
+    const role = state.user.role as Role;
+    const profileKey = this.userProfileService.getStateKeyByRole(role);
+
+    patchState({ isAuthorizationLoading: true, role });
+
+    const cachedProfile = sessionStorage.getItem('profile');
path: src/app/shared/store/registration.state.ts
user: login: humanmantis
body: use ngxs state instead to manipulate with session storage, add needed propretries to the state ans use SESSION_STORAGE_ENGINE to store them in session storage. 
created_at: 2025-04-23T14:10:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -28,7 +28,12 @@ export class ImagesService {
 
     return [
       {
-        path: entity instanceof Workshop ? 'assets/images/groupimages/workshop-img.png' : 'assets/images/groupimages/competition-img.png'
+        path:
+          entity.constructor.name === 'Workshop'
+            ? 'assets/images/groupimages/workshop-img.png'
+            : entity.constructor.name === 'Competition'
+              ? 'assets/images/groupimages/competition-img.png' // TODO: add competition image
+              : null
path: src/app/shared/services/images/images.service.ts
user: login: humanmantis
body: Sounds applicable, 
also we can move this to separate method to avoid nested ternaries 
created_at: 2025-04-23T14:14:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -192,33 +183,59 @@ export class RegistrationState {
     | Observable<Employee>
     | Observable<MinistryAdmin>
     | Observable<RegionAdmin>
-    | Observable<AreaAdmin> {
+    | Observable<AreaAdmin>
+    | void {
     const state = getState();
-    patchState({ role: state.user.role as Role });
-
-    switch (state.user.role) {
-      case Role.parent:
-        return this.parentService.getProfile().pipe(tap((parent: Parent) => patchState({ parent })));
-      case Role.provider:
-        return this.providerService.getProfile().pipe(tap((provider: Provider) => patchState({ provider })));
-      case Role.providerDeputy:
-      case Role.employee:
-        // TODO: Add provider patch when profile/provider id will be provided
-        return this.employeeService.getEmployeeById(state.user.id).pipe(tap((employee: Employee) => patchState({ employee })));
-      case Role.ministryAdmin:
-        return this.ministryAdminService.getAdminProfile().pipe(tap((ministryAdmin: MinistryAdmin) => patchState({ ministryAdmin })));
-      case Role.regionAdmin:
-        return this.regionAdminService.getAdminProfile().pipe(tap((regionAdmin: RegionAdmin) => patchState({ regionAdmin })));
-      case Role.areaAdmin:
-        return this.areaAdmin.getAdminProfile().pipe(tap((areaAdmin: AreaAdmin) => patchState({ areaAdmin })));
+    const role = state.user.role as Role;
+    const profileKey = this.userProfileService.getStateKeyByRole(role);
+
+    patchState({ isAuthorizationLoading: true, role });
+
+    const cachedProfile = sessionStorage.getItem('profile');
+
+    // if there is cached user profile
+    if (cachedProfile) {
+      const parsedProfile = JSON.parse(cachedProfile);
+      patchState({ [profileKey]: parsedProfile, isAuthorizationLoading: false });
+      return;
+    }
+
+    const profileObservable = this.userProfileService.getProfileObservableByRole(role, state.user.id);
+    if (!profileObservable) {
+      patchState({ isAuthorizationLoading: false });
+      return;
     }
+
+    return profileObservable.pipe(
+      tap((profile) => {
+        sessionStorage.setItem('profile', JSON.stringify(profile));
+        patchState({ [profileKey]: profile });
+      }),
+      catchError((error: HttpErrorResponse) => {
+        if (error.status === 401 || error.status === 403) {
path: src/app/shared/store/registration.state.ts
user: login: humanmantis
body: use HttpStatusCode enum
created_at: 2025-04-23T14:16:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -28,7 +28,12 @@ export class ImagesService {
 
     return [
       {
-        path: entity instanceof Workshop ? 'assets/images/groupimages/workshop-img.png' : 'assets/images/groupimages/competition-img.png'
+        path:
+          entity.constructor.name === 'Workshop'
+            ? 'assets/images/groupimages/workshop-img.png'
+            : entity.constructor.name === 'Competition'
+              ? 'assets/images/groupimages/competition-img.png' // TODO: add competition image
+              : null
path: src/app/shared/services/images/images.service.ts
user: login: coderabbitai[bot]
body: @humanmantis Agreed! Extracting the logic to a separate method would make the code much cleaner. Here's how it could look:

```typescript
private getDefaultImagePathForEntity(entity: Workshop | Provider | Competition): string {
  if (entity instanceof Workshop) {
    return 'assets/images/groupimages/workshop-img.png';
  } else if (entity instanceof Competition) {
    return 'assets/images/groupimages/competition-img.png'; // TODO: add competition image
  }
  return null;
}

public getCarouselImages(entity: Workshop | Provider | Competition): ImgPath[] {
  if (entity.imageIds?.length) {
    return entity.imageIds.map((imgId: string) => ({ path: environment.storageUrl + imgId }));
  }

  return [{ path: this.getDefaultImagePathForEntity(entity) }];
}
```

This approach combines both improvements - using `instanceof` for more robust type checking and eliminating the nested ternary operators for better readability.

---

<details>
<summary>✏️ Learnings added</summary>

```
Learnt from: humanmantis
PR: ita-social-projects/OoS-Frontend#2859
File: src/app/shared/services/images/images.service.ts:31-36
Timestamp: 2025-04-23T14:18:05.830Z
Learning: When working with type checking in the OoS-Frontend application, prefer using `instanceof` over `constructor.name` string comparison as it's more robust against minification and transpilation issues.
```

---

```
Learnt from: humanmantis
PR: ita-social-projects/OoS-Frontend#2859
File: src/app/shared/services/images/images.service.ts:31-36
Timestamp: 2025-04-23T14:18:05.830Z
Learning: Nested ternary operators should be avoided in the OoS-Frontend codebase. Extract complex conditional logic into separate, well-named methods to improve code readability and maintainability.
```

> Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure [review instructions](https://docs.coderabbit.ai/guides/review-instructions). You can manage existing learnings in the [app](https://app.coderabbit.ai/learnings).

</details>




<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer>@humanmantis Agreed! Extracting the logic to a separate method would make the code much cleaner. Here's how it could look:

```typescript
private getDefaultImagePathForEntity(entity: Workshop | Provider | Competition): string {
  if (entity instanceof Workshop) {
    return 'assets/images/groupimages/workshop-img.png';
  } else if (entity instanceof Competition) {
    return 'assets/images/groupimages/competition-img.png'; // TODO: add competition image
  }
  return null;
}

public getCarouselImages(entity: Workshop | Provider | Competition): ImgPath[] {
  if (entity.imageIds?.length) {
    return entity.imageIds.map((imgId: string) => ({ path: environment.storageUrl + imgId }));
  }

  return [{ path: this.getDefaultImagePathForEntity(entity) }];
}
```

This approach combines both improvements - using `instanceof` for more robust type checking and eliminating the nested ternary operators for better readability.

---

<details>
<summary>✏️ Learnings added</summary>

```
Learnt from: humanmantis
PR: ita-social-projects/OoS-Frontend#2859
File: src/app/shared/services/images/images.service.ts:31-36
Timestamp: 2025-04-23T14:18:05.830Z
Learning: When working with type checking in the OoS-Frontend application, prefer using `instanceof` over `constructor.name` string comparison as it's more robust against minification and transpilation issues.
```

---

```
Learnt from: humanmantis
PR: ita-social-projects/OoS-Frontend#2859
File: src/app/shared/services/images/images.service.ts:31-36
Timestamp: 2025-04-23T14:18:05.830Z
Learning: Nested ternary operators should be avoided in the OoS-Frontend codebase. Extract complex conditional logic into separate, well-named methods to improve code readability and maintainability.
```

> Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure [review instructions](https://docs.coderabbit.ai/guides/review-instructions). You can manage existing learnings in the [app](https://app.coderabbit.ai/learnings).

</details>

</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-04-23T14:18:07Z
author_association: NONE
diff_hunk: @@ -28,7 +28,12 @@ export class ImagesService {
 
     return [
       {
-        path: entity instanceof Workshop ? 'assets/images/groupimages/workshop-img.png' : 'assets/images/groupimages/competition-img.png'
+        path:
+          entity.constructor.name === 'Workshop'
+            ? 'assets/images/groupimages/workshop-img.png'
+            : entity.constructor.name === 'Competition'
+              ? 'assets/images/groupimages/competition-img.png' // TODO: add competition image
+              : null
path: src/app/shared/services/images/images.service.ts
user: login: YakymivY
body: turned out it is not my changes) i wonder how this commit appeared here
created_at: 2025-04-25T17:26:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -28,7 +28,12 @@ export class ImagesService {
 
     return [
       {
-        path: entity instanceof Workshop ? 'assets/images/groupimages/workshop-img.png' : 'assets/images/groupimages/competition-img.png'
+        path:
+          entity.constructor.name === 'Workshop'
+            ? 'assets/images/groupimages/workshop-img.png'
+            : entity.constructor.name === 'Competition'
+              ? 'assets/images/groupimages/competition-img.png' // TODO: add competition image
+              : null
path: src/app/shared/services/images/images.service.ts
user: login: coderabbitai[bot]
body: > Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

<!-- This is an auto-generated mute comment by CodeRabbit -->
created_at: 2025-04-25T17:26:39Z
author_association: NONE
diff_hunk: @@ -1,5 +1,32 @@
+import { AreaAdmin } from 'shared/models/area-admin.model';
+import { Employee } from 'shared/models/employee.model';
 import { MessageBarData } from 'shared/models/message-bar.model';
+import { MinistryAdmin } from 'shared/models/ministry-admin.model';
+import { Parent } from 'shared/models/parent.model';
+import { Provider } from 'shared/models/provider.model';
+import { RegionAdmin } from 'shared/models/region-admin.model';
 import { TimerData } from 'shared/models/server-error';
+import { User } from 'shared/models/user.model';
+
+export class SetProfile {
+  static readonly type = '[app] set profile';
+  constructor(public payload: Parent | Provider | Employee | MinistryAdmin | RegionAdmin | AreaAdmin) {}
+}
+
+export class ClearProfile {
+  static readonly type = '[app] clear profile';
+  constructor() {}
+}
path: src/app/shared/store/app.actions.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Remove useless empty constructors to satisfy Biome & reduce bundle size**

Static analysis (Biome `noUselessConstructor`) correctly flags these two classes.  
They carry no logic and only increase the emitted JavaScript.

```diff
 export class ClearProfile {
   static readonly type = '[app] clear profile';
-  constructor() {}
 }
...
 export class ClearPersonalInfo {
   static readonly type = '[app] clear personal info';
-  constructor() {}
 }
```



Also applies to: 26-29

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 18-18: This constructor is unnecessary.

Unsafe fix: Remove the unnecessary constructor.


(lint/complexity/noUselessConstructor)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-30T06:12:15Z
author_association: NONE
diff_hunk: @@ -1,5 +1,32 @@
+import { AreaAdmin } from 'shared/models/area-admin.model';
+import { Employee } from 'shared/models/employee.model';
 import { MessageBarData } from 'shared/models/message-bar.model';
+import { MinistryAdmin } from 'shared/models/ministry-admin.model';
+import { Parent } from 'shared/models/parent.model';
+import { Provider } from 'shared/models/provider.model';
+import { RegionAdmin } from 'shared/models/region-admin.model';
 import { TimerData } from 'shared/models/server-error';
+import { User } from 'shared/models/user.model';
+
+export class SetProfile {
+  static readonly type = '[app] set profile';
+  constructor(public payload: Parent | Provider | Employee | MinistryAdmin | RegionAdmin | AreaAdmin) {}
+}
path: src/app/shared/store/app.actions.ts
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**`TechAdmin` profile type missing in the union**  

`SetProfile.payload` currently omits `TechAdmin`, yet `RegistrationState` still contains that role.  Dispatching `SetProfile(new TechAdmin(...))` will therefore break type-safety.

```diff
-constructor(public payload: Parent | Provider | Employee | MinistryAdmin | RegionAdmin | AreaAdmin) {}
+constructor(
+  public payload:
+    | Parent
+    | Provider
+    | Employee
+    | MinistryAdmin
+    | RegionAdmin
+    | AreaAdmin
+    | TechAdmin /* add */
+) {}
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
export class SetProfile {
  static readonly type = '[app] set profile';
  constructor(
    public payload:
      | Parent
      | Provider
      | Employee
      | MinistryAdmin
      | RegionAdmin
      | AreaAdmin
      | TechAdmin
  ) {}
}
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-30T06:12:15Z
author_association: NONE
diff_hunk: @@ -184,41 +177,59 @@ export class RegistrationState {
 
   @Action(GetProfile)
   getProfile({
+    dispatch,
     patchState,
     getState
-  }: StateContext<RegistrationStateModel>):
-    | Observable<Parent>
-    | Observable<Provider>
-    | Observable<Employee>
-    | Observable<MinistryAdmin>
-    | Observable<RegionAdmin>
-    | Observable<AreaAdmin> {
+  }: StateContext<RegistrationStateModel>): Observable<Parent | Provider | Employee | MinistryAdmin | RegionAdmin | AreaAdmin> {
     const state = getState();
-    patchState({ role: state.user.role as Role });
-
-    switch (state.user.role) {
-      case Role.parent:
-        return this.parentService.getProfile().pipe(tap((parent: Parent) => patchState({ parent })));
-      case Role.provider:
-        return this.providerService.getProfile().pipe(tap((provider: Provider) => patchState({ provider })));
-      case Role.providerDeputy:
-      case Role.employee:
-        // TODO: Add provider patch when profile/provider id will be provided
-        return this.employeeService.getEmployeeById(state.user.id).pipe(tap((employee: Employee) => patchState({ employee })));
-      case Role.ministryAdmin:
-        return this.ministryAdminService.getAdminProfile().pipe(tap((ministryAdmin: MinistryAdmin) => patchState({ ministryAdmin })));
-      case Role.regionAdmin:
-        return this.regionAdminService.getAdminProfile().pipe(tap((regionAdmin: RegionAdmin) => patchState({ regionAdmin })));
-      case Role.areaAdmin:
-        return this.areaAdmin.getAdminProfile().pipe(tap((areaAdmin: AreaAdmin) => patchState({ areaAdmin })));
+    const role = state.user.role as Role;
+    const profileKey = this.userProfileService.getStateKeyByRole(role);
+
+    patchState({ isAuthorizationLoading: true, role });
+
+    const cachedProfile = this.store.selectSnapshot(AppState.profile);
+
+    // if there is cached user profile
+    if (cachedProfile) {
+      patchState({ [profileKey]: cachedProfile, isAuthorizationLoading: false });
+      return;
+    }
+
+    const profileObservable = this.userProfileService.getProfileObservableByRole(role, state.user.id);
+    if (!profileObservable) {
+      patchState({ isAuthorizationLoading: false });
+      return;
path: src/app/shared/store/registration.state.ts
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**`getProfile` returns `undefined` in some paths – breaks declared return type**

Early `return;` statements violate the declared return type `Observable<...>`.  
This compiles only with `noImplicitReturns: false` or by relying on implicit `any`, and it makes the method signature misleading for callers.

Suggested fix: include `undefined` in the return type _or_ return an `EMPTY` observable to keep the observable contract.

```diff
-  }: StateContext<RegistrationStateModel>): Observable<Parent | Provider | Employee | MinistryAdmin | RegionAdmin | AreaAdmin> {
+  }: StateContext<RegistrationStateModel>): Observable<
+    Parent | Provider | Employee | MinistryAdmin | RegionAdmin | AreaAdmin
+  > | undefined {
...
-      return;
+      return undefined;
...
-      return;
+      return undefined;
```

Optionally:

```typescript
import { EMPTY } from 'rxjs';
...
return EMPTY; // keeps observable contract
```

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-30T06:12:15Z
author_association: NONE
diff_hunk: @@ -16,6 +36,8 @@ export interface AppStateModel {
 @State<AppStateModel>({
   name: 'app',
   defaults: {
+    profile: undefined,
path: src/app/shared/store/app.state.ts
user: login: humanmantis
body: can we use null as initial value?
created_at: 2025-05-03T08:15:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -184,41 +177,59 @@ export class RegistrationState {
 
   @Action(GetProfile)
   getProfile({
+    dispatch,
     patchState,
     getState
-  }: StateContext<RegistrationStateModel>):
-    | Observable<Parent>
-    | Observable<Provider>
-    | Observable<Employee>
-    | Observable<MinistryAdmin>
-    | Observable<RegionAdmin>
-    | Observable<AreaAdmin> {
+  }: StateContext<RegistrationStateModel>): Observable<Parent | Provider | Employee | MinistryAdmin | RegionAdmin | AreaAdmin> {
     const state = getState();
-    patchState({ role: state.user.role as Role });
-
-    switch (state.user.role) {
-      case Role.parent:
-        return this.parentService.getProfile().pipe(tap((parent: Parent) => patchState({ parent })));
-      case Role.provider:
-        return this.providerService.getProfile().pipe(tap((provider: Provider) => patchState({ provider })));
-      case Role.providerDeputy:
-      case Role.employee:
-        // TODO: Add provider patch when profile/provider id will be provided
-        return this.employeeService.getEmployeeById(state.user.id).pipe(tap((employee: Employee) => patchState({ employee })));
-      case Role.ministryAdmin:
-        return this.ministryAdminService.getAdminProfile().pipe(tap((ministryAdmin: MinistryAdmin) => patchState({ ministryAdmin })));
-      case Role.regionAdmin:
-        return this.regionAdminService.getAdminProfile().pipe(tap((regionAdmin: RegionAdmin) => patchState({ regionAdmin })));
-      case Role.areaAdmin:
-        return this.areaAdmin.getAdminProfile().pipe(tap((areaAdmin: AreaAdmin) => patchState({ areaAdmin })));
+    const role = state.user.role as Role;
+    const profileKey = this.userProfileService.getStateKeyByRole(role);
+
+    patchState({ isAuthorizationLoading: true, role });
+
+    const cachedProfile = this.store.selectSnapshot(AppState.profile);
+
+    // if there is cached user profile
+    if (cachedProfile) {
+      patchState({ [profileKey]: cachedProfile, isAuthorizationLoading: false });
+      return;
+    }
+
+    const profileObservable = this.userProfileService.getProfileObservableByRole(role, state.user.id);
+    if (!profileObservable) {
+      patchState({ isAuthorizationLoading: false });
+      return;
     }
+
+    return profileObservable.pipe(
+      tap((profile: Parent | Provider | Employee | MinistryAdmin | RegionAdmin | AreaAdmin) => {
+        dispatch(new SetProfile(profile));
+        patchState({ [profileKey]: profile });
+      }),
+      catchError((error: HttpErrorResponse) => {
+        if (error.status === HttpStatusCode.Unauthorized || error.status === HttpStatusCode.Forbidden) {
+          this.router.navigate(['/forbidden']);
+        }
+        return throwError(() => new Error('Something went wrong!'));
path: src/app/shared/store/registration.state.ts
user: login: humanmantis
body: it may be better to throw the original error
created_at: 2025-05-03T08:16:27Z
author_association: CONTRIBUTOR

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-04-14T09:49:25Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough

This update introduces new user experience enhancements and refactors user profile management in the application. It adds dedicated components for forbidden and profile waiting pages, integrates a unified `UserProfileService` for role-based profile retrieval, and extends the global state with profile and personal information handling. The routing is updated to include a forbidden page, and conditional rendering is implemented to show a loading state during authorization checks. Associated tests and styles for the new components are provided, and the Angular Material progress spinner module is included for visual feedback during loading states.

## Changes

| File(s)                                                                                  | Change Summary |
|------------------------------------------------------------------------------------------|---------------|
| `src/app/app.component.html`, `src/app/app.component.scss`                               | Refactored main layout to show a waiting page during authorization loading and removed the `.hide-content` class. |
| `src/app/shared/components/forbidden-page/forbidden-page.component.*`                    | Added a new forbidden page component with HTML template, SCSS styles, TypeScript logic, and unit tests. |
| `src/app/shared/components/profile-waiting-page/profile-waiting-page.component.*`        | Added a new profile waiting page component with HTML template, SCSS styles, TypeScript logic, and unit test. |
| `src/app/shared/services/user/user-profile.service.ts`,<br>`src/app/shared/services/user/user-profile.service.spec.ts` | Introduced a unified service for role-based user profile retrieval with comprehensive unit tests. |
| `src/app/shared/store/registration.state.ts`                                             | Refactored to use `UserProfileService` for profile fetching, streamlined state and error handling, and improved caching logic. |
| `src/app/shared/store/app.actions.ts`                                                    | Added actions for setting and clearing user profile and personal info in the global state. |
| `src/app/shared/store/app.state.ts`                                                      | Extended the state model and selectors to include user profile and personal info, with new action handlers. |
| `src/app/shared/shared.module.ts`                                                        | Declared and exported new waiting and forbidden page components in the shared module. |
| `src/app/shared/modules/material.module.ts`                                              | Imported and exported the Angular Material progress spinner module. |
| `src/app/shell/shell-routing.module.ts`                                                  | Replaced the server error page route with a forbidden page and removed competition creation routes. |
| `src/app/shell/details/competition-details/competition-details.component.ts`,<br>`src/app/shell/details/details-tabs/provider-about/provider-about.component.ts` | Ensured correct prototype assignment before generating carousel images for competition and provider entities. |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant User
    participant AppComponent
    participant RegistrationState
    participant UserProfileService
    participant Store
    participant ProfileWaitingPage
    participant ForbiddenPage

    User->>AppComponent: Load Application
    AppComponent->>RegistrationState: Check Authorization
    RegistrationState->>UserProfileService: Get Profile Observable by Role
    UserProfileService-->>RegistrationState: Observable<Profile>
    RegistrationState->>Store: Set Profile in State
    AppComponent->>AppComponent: isAuthorizationLoading$ = true
    AppComponent-->>ProfileWaitingPage: Show waiting page
    AppComponent->>AppComponent: isAuthorizationLoading$ = false
    AppComponent-->>MainContent: Show main content

    alt Unauthorized or Forbidden
        RegistrationState->>AppComponent: Navigate to /forbidden
        AppComponent-->>ForbiddenPage: Show forbidden page
    end
```

## Suggested reviewers

- DmyMi  
- humanmantis

## Poem

> A rabbit hops through code so bright,  
> New pages bloom for wrongs and right—  
> Forbidden doors now gently close,  
> While loading spinners softly glow.  
> Profiles fetched with nimble care,  
> And state is managed everywhere.  
> 🐇 Hooray for code that’s clean and neat—  
> This update makes the app complete!

<!-- walkthrough_end -->

<!-- announcements_start -->

> [!TIP]
> <details>
> <summary>⚡️ Faster reviews with caching</summary>
> 
> - CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 16th. To opt out, configure `Review - Disable Cache` at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the `Data Retention` setting under your Organization Settings.
> 
> Enjoy the performance boost—your workflow just got faster.
> 
> </details>

<!-- announcements_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=ita-social-projects/OoS-Frontend&utm_content=2859):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Explain this complex logic.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai explain this code block.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and explain its main purpose.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE
user: login: sonarqubecloud[bot]
created_at: 2025-05-13T07:25:41Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=ita-social-projects-oos-frontend&pullRequest=2859) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [11 New issues](https://sonarcloud.io/project/issues?id=ita-social-projects-oos-frontend&pullRequest=2859&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=ita-social-projects-oos-frontend&pullRequest=2859&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=ita-social-projects-oos-frontend&pullRequest=2859&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [88.1% Coverage on New Code](https://sonarcloud.io/component_measures?id=ita-social-projects-oos-frontend&pullRequest=2859&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=ita-social-projects-oos-frontend&pullRequest=2859&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=ita-social-projects-oos-frontend&pullRequest=2859)


author_association: NONE