diff --git a/angular.json b/angular.json index 0d8a628e48..aea784c747 100644 --- a/angular.json +++ b/angular.json @@ -35,7 +35,11 @@ "src/favicon.ico", "src/assets" ], - "styles": ["./node_modules/leaflet/dist/leaflet.css", "src/styles.scss"], + "styles": [ + "./node_modules/leaflet/dist/leaflet.css", + "./node_modules/ngx-owl-carousel-o/lib/styles/prebuilt-themes/owl.carousel.min.css", + "./node_modules/ngx-owl-carousel-o/lib/styles/prebuilt-themes/owl.theme.default.min.css", + "src/styles.scss"], "scripts": [], "vendorChunk": true, "extractLicenses": false, diff --git a/cloudbuild-old.yaml b/cloudbuild-old.yaml index 1e335c6ba8..80aa2b7f3c 100644 --- a/cloudbuild-old.yaml +++ b/cloudbuild-old.yaml @@ -13,7 +13,7 @@ steps: }; EOF # Build the container image - - name: 'gcr.io/k8s-skaffold/pack' + - name: 'buildpacksio/pack' entrypoint: 'pack' args: - build diff --git a/cloudbuild.yaml b/cloudbuild.yaml index 84c6d593b4..4b326fe57b 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -13,13 +13,13 @@ steps: }; EOF # Build the container image - - name: 'gcr.io/k8s-skaffold/pack' + - name: 'buildpacksio/pack' entrypoint: 'pack' args: - build - ${_REGION}-docker.pkg.dev/$PROJECT_ID/outofschool/oos-frontend:$SHORT_SHA - --cache-image=${_REGION}-docker.pkg.dev/$PROJECT_ID/outofschool/oos-cache:front - - --builder=paketobuildpacks/builder:base + - --builder=paketobuildpacks/builder-jammy-base:latest - --buildpack=paketo-buildpacks/web-servers - --buildpack=gcr.io/paketo-buildpacks/environment-variables - --env=BP_NODE_RUN_SCRIPTS=build:qa diff --git a/package.json b/package.json index 5a9e68f857..238df0115d 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "build": "ng build", "build:prod": "ng build --configuration production", "build:qa": "ng build --configuration qa", + "build:local": "ng build --configuration local", "test": "jest", "test:coverage": "jest --coverage", "test-watch": "jest --watch", @@ -21,11 +22,10 @@ "dependencies": { "@angular-slider/ngx-slider": "^16.0.1", "@angular/animations": "^16.2.12", - "@angular/cdk": "^16.2.14", + "@angular/cdk": "16.2.14", "@angular/common": "^16.2.12", "@angular/compiler": "^16.2.12", "@angular/core": "^16.2.12", - "@angular/flex-layout": "^15.0.0-beta.42", "@angular/forms": "^16.2.12", "@angular/material": "^16.2.14", "@angular/material-moment-adapter": "^16.2.14", @@ -44,10 +44,12 @@ "leaflet": "^1.9.4", "libphonenumber-js": "^1.10.51", "moment": "^2.30.1", - "ngx-image-cropper": "7.2.1", + "ngx-image-cropper": "8.0.0", "ngx-mat-intl-tel-input": "^5.0.0", "ngx-mat-timepicker": "^16.2.0", + "ngx-owl-carousel-o": "16.0.0", "ngx-pagination": "^6.0.3", + "ngx-window-token": "^7.0.0", "rxjs": "~7.8.0", "tslib": "^2.6.2", "zone.js": "~0.13.0" @@ -85,6 +87,7 @@ "karma-coverage": "~2.2.1", "lint-staged": "^15.2.7", "prettier": "^3.2.5", + "tailwindcss": "^3.4.15", "ts-jest": "^29.1.2", "ts-node": "~10.9.2", "typescript": "~5.1.3" diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 5d76fc6d96..8c8b0ac4ad 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -1,10 +1,20 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; -const routes: Routes = [{ path: 'data', loadChildren: () => import('./shell/admin-tools/data/data.module').then((m) => m.DataModule) }]; +const routes: Routes = [ + { + path: 'data', + loadChildren: () => import('./shell/admin-tools/data/data.module').then((m) => m.DataModule) + } +]; @NgModule({ - imports: [RouterModule.forRoot(routes, { useHash: true, scrollPositionRestoration: 'enabled' })], + imports: [ + RouterModule.forRoot(routes, { + useHash: true, + anchorScrolling: 'enabled' + }) + ], exports: [RouterModule] }) export class AppRoutingModule {} diff --git a/src/app/app.component.html b/src/app/app.component.html index 5eee7961a8..65bc19de25 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,5 +1,5 @@ -
+
diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts index 5e9bffafa7..b6c29bf0e5 100644 --- a/src/app/app.component.spec.ts +++ b/src/app/app.component.spec.ts @@ -9,6 +9,9 @@ import { RouterTestingModule } from '@angular/router/testing'; import { TranslateModule } from '@ngx-translate/core'; import { NgxsModule, Store } from '@ngxs/store'; +import { NavigationEnd, Router } from '@angular/router'; +import { ViewportScroller } from '@angular/common'; +import { Subject } from 'rxjs'; import { AppComponent } from './app.component'; describe('AppComponent', () => { @@ -16,8 +19,12 @@ describe('AppComponent', () => { let fixture: ComponentFixture; let store: Store; let mockMatSnackBar: MatSnackBar; + let routerEvents$: Subject; + let viewportScroller: ViewportScroller; beforeEach(async () => { + routerEvents$ = new Subject(); + await TestBed.configureTestingModule({ imports: [ RouterTestingModule, @@ -36,7 +43,14 @@ describe('AppComponent', () => { MockSidenavComponent, MockSidenavFilterComponent ], - providers: [DateAdapter] + providers: [ + DateAdapter, + { provide: Router, useValue: { events: routerEvents$.asObservable() } }, + { + provide: ViewportScroller, + useValue: { scrollToPosition: jest.fn() } + } + ] }).compileComponents(); }); @@ -45,9 +59,14 @@ describe('AppComponent', () => { component = fixture.componentInstance; store = TestBed.inject(Store); mockMatSnackBar = TestBed.inject(MatSnackBar); + viewportScroller = TestBed.inject(ViewportScroller); fixture.detectChanges(); }); + afterEach(() => { + routerEvents$.complete(); // Завершуємо потік подій після кожного тесту + }); + it('should create the app', () => { expect(component).toBeTruthy(); }); @@ -61,6 +80,26 @@ describe('AppComponent', () => { expect(component.onResize).toHaveBeenCalled(); expect(component.isMobileView).toBeTruthy(); }); + + it('should not scroll to top if routes are in ignore list', () => { + const previousEvent = new NavigationEnd(1, '/result', '/result'); + const currentEvent = new NavigationEnd(2, '/result', '/result'); + + routerEvents$.next(previousEvent); + routerEvents$.next(currentEvent); + + expect(viewportScroller.scrollToPosition).not.toHaveBeenCalled(); + }); + + it('should scroll to top if routes are not in ignore list', () => { + const previousEvent = new NavigationEnd(1, '/not-ignore', '/not-ignore'); + const currentEvent = new NavigationEnd(2, '/not-ignore', '/not-ignore'); + + routerEvents$.next(previousEvent); + routerEvents$.next(currentEvent); + + expect(viewportScroller.scrollToPosition).toHaveBeenCalledWith([0, 0]); + }); }); @Component({ diff --git a/src/app/app.component.ts b/src/app/app.component.ts index b9c789cbf0..e522422364 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,14 +1,16 @@ import { Component, HostListener, OnDestroy, OnInit } from '@angular/core'; import { DateAdapter } from '@angular/material/core'; -import { Router } from '@angular/router'; +import { Event, NavigationEnd, Router } from '@angular/router'; import { TranslateService } from '@ngx-translate/core'; import { Select, Store } from '@ngxs/store'; -import { Observable, Subject } from 'rxjs'; +import { Observable, pairwise, Subject } from 'rxjs'; import { ToggleMobileScreen } from 'shared/store/app.actions'; import { GetFeaturesList } from 'shared/store/meta-data.actions'; import { CheckAuth } from 'shared/store/registration.actions'; import { RegistrationState } from 'shared/store/registration.state'; +import { ViewportScroller } from '@angular/common'; +import { filter } from 'rxjs/operators'; @Component({ selector: 'app-root', @@ -23,13 +25,32 @@ export class AppComponent implements OnInit, OnDestroy { private destroy$: Subject = new Subject(); private previousMobileScreenValue: boolean; private selectedLanguage: string; + private readonly ignoreScrollToTopRoutes = ['/result']; constructor( private store: Store, private translateService: TranslateService, private dateAdapter: DateAdapter, - private router: Router - ) {} + private router: Router, + private viewportScroller: ViewportScroller + ) { + this.router.events + .pipe( + filter((event: Event) => event instanceof NavigationEnd), + pairwise() + ) + .subscribe(([previousEvent, currentEvent]: [NavigationEnd, NavigationEnd]) => { + const previousUrl = previousEvent.urlAfterRedirects; + const currentUrl = currentEvent.urlAfterRedirects; + + if ( + !this.ignoreScrollToTopRoutes.some((route) => previousUrl.includes(route)) || + !this.ignoreScrollToTopRoutes.some((route) => currentUrl.includes(route)) + ) { + this.viewportScroller.scrollToPosition([0, 0]); + } + }); + } @HostListener('window: resize', ['$event.target']) public onResize(event: Window): void { @@ -48,7 +69,7 @@ export class AppComponent implements OnInit, OnDestroy { * method defined window.width and assign isMobileView: boolean */ public isWindowMobile(event: Window): void { - this.isMobileView = event.innerWidth < 750; + this.isMobileView = event.innerWidth < 800; if (this.previousMobileScreenValue !== this.isMobileView) { this.store.dispatch(new ToggleMobileScreen(this.isMobileView)); this.previousMobileScreenValue = this.isMobileView; diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 5f8afe8304..cb5bb9dc45 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -1,8 +1,7 @@ import { registerLocaleData } from '@angular/common'; import { HTTP_INTERCEPTORS, HttpClient, HttpClientModule } from '@angular/common/http'; import localeUk from '@angular/common/locales/uk'; -import { LOCALE_ID, NgModule } from '@angular/core'; -import { FlexLayoutModule } from '@angular/flex-layout'; +import { APP_INITIALIZER, LOCALE_ID, NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { MAT_LEGACY_SELECT_CONFIG as MAT_SELECT_CONFIG } from '@angular/material/legacy-select'; import { BrowserModule } from '@angular/platform-browser'; @@ -11,8 +10,9 @@ import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { TranslateHttpLoader } from '@ngx-translate/http-loader'; import { NgxsReduxDevtoolsPluginModule } from '@ngxs/devtools-plugin'; import { NgxsLoggerPluginModule } from '@ngxs/logger-plugin'; -import { NgxsStoragePluginModule, StorageOption } from '@ngxs/storage-plugin'; -import { NgxsModule } from '@ngxs/store'; +import { NgxsStoragePluginModule, LOCAL_STORAGE_ENGINE, SESSION_STORAGE_ENGINE } from '@ngxs/storage-plugin'; +import { NgxsModule, Store } from '@ngxs/store'; +import { Observable } from 'rxjs'; import { ErrorHandleInterceptor } from 'shared/interceptors/error-handle.interceptor'; import { RegistrationModule } from 'shared/modules/registration.module'; @@ -27,6 +27,7 @@ import { NavigationState } from 'shared/store/navigation.state'; import { NotificationState } from 'shared/store/notification.state'; import { ParentState } from 'shared/store/parent.state'; import { ProviderState } from 'shared/store/provider.state'; +import { CheckAuth } from 'shared/store/registration.actions'; import { RegistrationState } from 'shared/store/registration.state'; import { SharedUserState } from 'shared/store/shared-user.state'; import { environment } from '../environments/environment'; @@ -64,8 +65,16 @@ registerLocaleData(localeUk); ]), NgxsStoragePluginModule.forRoot({ - key: AppState, - storage: StorageOption.SessionStorage + key: [ + { + key: AppState, + engine: SESSION_STORAGE_ENGINE + }, + { + key: 'filter.previousResults', + engine: LOCAL_STORAGE_ENGINE + } + ] }), NgxsReduxDevtoolsPluginModule.forRoot({ disabled: environment.production @@ -73,7 +82,6 @@ registerLocaleData(localeUk); NgxsLoggerPluginModule.forRoot({ disabled: environment.production }), - FlexLayoutModule, ShellModule, RegistrationModule, HttpClientModule, @@ -91,6 +99,12 @@ registerLocaleData(localeUk); provide: MAT_SELECT_CONFIG, useValue: { overlayPanelClass: 'custom-overlay-panel' } }, + { + provide: APP_INITIALIZER, + useFactory: (store: Store) => (): Observable => store.dispatch(new CheckAuth()), + deps: [Store], + multi: true + }, { provide: HTTP_INTERCEPTORS, useClass: ErrorHandleInterceptor, diff --git a/src/app/footer/footer.component.html b/src/app/footer/footer.component.html index 65d6596fe6..f6e9041b4e 100644 --- a/src/app/footer/footer.component.html +++ b/src/app/footer/footer.component.html @@ -1,8 +1,8 @@
{{ 'TRIAL_MESSAGE' | translate }}
-