Skip to content

Commit a254455

Browse files
ZhmenZHsergeimelnik1980github-actions[bot]Alexveselun
authored
Feat: implemented cookie consent banner (#287)
* fix: banner image and images on blog page (#277) * feat(ci): add automerge * feat(hotfix): the year on the footer updated * fix automerge * change github token * revert github token * Feat: implemented cookie consent banner * feat: removed unused code * feat: fixed comments * fix: triggering google analytics on the route after allowing cookies --------- Co-authored-by: Sergey Melnik <sergey.melnik@valor-software.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Oleksandr Yankevych <alexveselun@gmail.com> Co-authored-by: Sergei Melnik <sergei.melnik1980@gmail.com>
1 parent 9146a88 commit a254455

13 files changed

Lines changed: 361 additions & 169 deletions

File tree

.github/workflows/auto-merge.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: automerge
2+
on:
3+
pull_request:
4+
types:
5+
- labeled
6+
- unlocked
7+
pull_request_review:
8+
types:
9+
- submitted
10+
check_suite:
11+
types:
12+
- completed
13+
status: {}
14+
jobs:
15+
automerge:
16+
runs-on: ubuntu-latest
17+
steps:
18+
- id: automerge
19+
name: automerge
20+
uses: "pascalgn/automerge-action@v0.15.6"
21+
env:
22+
GITHUB_TOKEN: "${{ secrets.PAT_GITHUB_TOKEN }}"
23+
MERGE_LABELS: "automerge"
24+
MERGE_REMOVE_LABELS: "automerge"
25+
MERGE_METHOD: "merge"
26+
MERGE_COMMIT_MESSAGE: "pull-request-description"
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
<top-menu *ngIf="!showFooterAndHeader"></top-menu>
22
<div class="font-workSans" style="min-height: 100%">
3-
<router-outlet></router-outlet>
3+
<router-outlet></router-outlet>
44
</div>
55
<app-footer *ngIf="!showFooterAndHeader"></app-footer>
66

7+
<cookie-consent-banner *ngIf="showCookieConsentBanner | async"
8+
(acceptCookies)="onAcceptCookies()">
9+
</cookie-consent-banner>
10+
711

812

Lines changed: 102 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,101 +1,120 @@
1-
import { Component, Inject, HostListener, OnDestroy, AfterViewInit } from '@angular/core';
2-
import { SeoService } from "@valor-software/common-docs";
1+
import { AfterViewInit, Component, HostListener, OnDestroy } from '@angular/core';
2+
import { SeoService } from '@valor-software/common-docs';
33
import { NavigationEnd, Router, RoutesRecognized } from '@angular/router';
4-
import { DOCUMENT } from '@angular/common';
5-
import { filter, pairwise } from "rxjs/operators";
6-
import { Subscription } from "rxjs";
4+
import { filter, pairwise } from 'rxjs/operators';
5+
import { BehaviorSubject, Subscription } from 'rxjs';
6+
import { CookieService } from 'ngx-cookie-service';
77

88
const notFoundPageUrl = '/404';
99
declare const gtag: any;
10+
const isCookieAllowed = 'isCookieAllowed';
1011

1112
@Component({
12-
selector: 'valor-software-site-base-root',
13-
templateUrl: './app.component.html',
14-
styleUrls: ['./app.component.scss'],
13+
selector: 'valor-software-site-base-root',
14+
templateUrl: './app.component.html',
15+
styleUrls: ['./app.component.scss'],
1516
})
1617
export class AppComponent implements OnDestroy, AfterViewInit {
17-
title = 'valor-software-site';
18-
scrollPosition = 0;
19-
$routeEvents?: Subscription;
20-
$gaRouteEvents?: Subscription;
21-
$routerEventNavigationEnd: Subscription;
22-
showFooterAndHeader = true;
18+
title = 'valor-software-site';
19+
scrollPosition = 0;
20+
$routeEvents?: Subscription;
21+
$gaRouteEvents?: Subscription;
22+
$routerEventNavigationEnd: Subscription;
23+
showFooterAndHeader = true;
24+
showCookieConsentBanner = new BehaviorSubject(!this.cookieService.get(isCookieAllowed));
2325

24-
@HostListener('window:beforeunload') private onReload() {
25-
window.sessionStorage.setItem('scrollPosition', window.scrollY.toString());
26-
}
26+
@HostListener('window:beforeunload')
27+
private onReload() {
28+
window.sessionStorage.setItem('scrollPosition', window.scrollY.toString());
29+
}
2730

28-
constructor(
29-
private seo: SeoService,
30-
private router: Router,
31-
@Inject(DOCUMENT) private document: any
32-
) {
33-
const scrollItem = window.sessionStorage.getItem('scrollPosition');
34-
this.scrollPosition = scrollItem || scrollItem == '0' ? Number(scrollItem) : 0;
35-
this.$routerEventNavigationEnd = router.events.pipe(
36-
filter(evt => evt instanceof NavigationEnd)
37-
).subscribe(res => {
38-
this.showFooterAndHeader = !!(this.router.url === notFoundPageUrl);
39-
});
40-
}
31+
constructor(
32+
private seo: SeoService,
33+
private router: Router,
34+
private readonly cookieService: CookieService,
35+
) {
36+
const scrollItem = window.sessionStorage.getItem('scrollPosition');
37+
this.scrollPosition = scrollItem || scrollItem == '0' ? Number(scrollItem) : 0;
38+
this.$routerEventNavigationEnd = router.events.pipe(
39+
filter(evt => evt instanceof NavigationEnd)
40+
).subscribe(res => {
41+
this.showFooterAndHeader = !!(this.router.url === notFoundPageUrl);
42+
});
43+
}
4144

42-
scrollToPosition(value: number) {
43-
setTimeout(() => {
44-
window.scrollTo(0, value);
45-
window.sessionStorage.removeItem('scrollPosition');
46-
}, 50);
47-
}
45+
ngAfterViewInit() {
46+
this.cookieService.get(isCookieAllowed)
47+
? this.handleGoogleAnalyticsTracking()
48+
: this.cookieService.deleteAll();
4849

49-
ngAfterViewInit() {
50-
this.handleGoogleAnalyticsTracking();
51-
setTimeout(() => {
52-
if (this.scrollPosition) {
53-
this.scrollToPosition(this.scrollPosition);
54-
}
55-
}, 300);
50+
setTimeout(() => {
51+
if (this.scrollPosition) {
52+
this.scrollToPosition(this.scrollPosition);
53+
}
54+
}, 300);
5655

57-
this.$routeEvents = this.router.events
58-
.pipe(filter((evt: any) => evt instanceof RoutesRecognized), pairwise())
59-
.subscribe((events: RoutesRecognized[]) => {
60-
const previousUrl = events[0].urlAfterRedirects;
61-
const currentUrl = events[1].urlAfterRedirects;
62-
if (previousUrl === currentUrl) {
63-
return;
64-
}
56+
this.$routeEvents = this.router.events
57+
.pipe(filter((evt: any) => evt instanceof RoutesRecognized), pairwise())
58+
.subscribe((events: RoutesRecognized[]) => {
59+
const previousUrl = events[0].urlAfterRedirects;
60+
const currentUrl = events[1].urlAfterRedirects;
61+
if (previousUrl === currentUrl) {
62+
return;
63+
}
6564

66-
const previousFragment = this.router.parseUrl(events[0].urlAfterRedirects).fragment;
67-
const currentFragment = this.router.parseUrl(events[1].urlAfterRedirects).fragment;
68-
const previousRouteUrl = this.router.parseUrl(events[0].urlAfterRedirects).root.children["primary"]?.segments[0].path;
69-
const currentRouteUrl = this.router.parseUrl(events[1].urlAfterRedirects).root.children["primary"]?.segments[0].path;
70-
if (previousUrl !== currentUrl && previousRouteUrl === currentRouteUrl) {
71-
if (previousFragment || currentFragment) {
72-
return;
73-
}
74-
}
65+
const previousFragment = this.router.parseUrl(events[0].urlAfterRedirects).fragment;
66+
const currentFragment = this.router.parseUrl(events[1].urlAfterRedirects).fragment;
67+
const previousRouteUrl = this.router.parseUrl(events[0].urlAfterRedirects).root.children['primary']?.segments[0].path;
68+
const currentRouteUrl = this.router.parseUrl(events[1].urlAfterRedirects).root.children['primary']?.segments[0].path;
69+
if (previousUrl !== currentUrl && previousRouteUrl === currentRouteUrl) {
70+
if (previousFragment || currentFragment) {
71+
return;
72+
}
73+
}
7574

76-
this.scrollToPosition(0);
77-
});
78-
}
75+
this.scrollToPosition(0);
76+
});
77+
}
7978

80-
ngOnDestroy() {
81-
this.$routeEvents?.unsubscribe();
82-
this.$routerEventNavigationEnd?.unsubscribe();
83-
this.$gaRouteEvents?.unsubscribe();
84-
}
79+
ngOnDestroy() {
80+
this.$routeEvents?.unsubscribe();
81+
this.$routerEventNavigationEnd?.unsubscribe();
82+
this.$gaRouteEvents?.unsubscribe();
83+
}
8584

86-
private handleGoogleAnalyticsTracking(): void {
87-
let configured = false;
88-
this.$gaRouteEvents = this.router.events.subscribe((event) => {
89-
if (event instanceof NavigationEnd) {
90-
if(!configured){
91-
gtag('config', 'UA-73071494-2', { send_page_view: false });
92-
configured = true;
93-
}
94-
gtag('event', 'page_view', {
95-
page_location: window.location.href,
96-
page_path: event.urlAfterRedirects,
97-
});
98-
}
99-
});
100-
}
85+
onAcceptCookies(): void {
86+
this.cookieService.set(isCookieAllowed, 'true');
87+
this.showCookieConsentBanner.next(false);
88+
/** make navigation to current url to trigger googleAnalytics on current route */
89+
this.redirectTo(this.router.url);
90+
this.handleGoogleAnalyticsTracking();
91+
}
92+
93+
private scrollToPosition(value: number): void {
94+
setTimeout(() => {
95+
window.scrollTo(0, value);
96+
window.sessionStorage.removeItem('scrollPosition');
97+
}, 50);
98+
}
99+
100+
private handleGoogleAnalyticsTracking(): void {
101+
let configured = false;
102+
this.$gaRouteEvents = this.router.events.subscribe((event) => {
103+
if (event instanceof NavigationEnd) {
104+
if (!configured) {
105+
gtag('config', 'UA-73071494-2', { send_page_view: false });
106+
configured = true;
107+
}
108+
gtag('event', 'page_view', {
109+
page_location: window.location.href,
110+
page_path: event.urlAfterRedirects,
111+
});
112+
}
113+
});
114+
}
115+
116+
private redirectTo(uri: string): void {
117+
this.router.navigateByUrl('/', { skipLocationChange: true })
118+
.then(() => this.router.navigate([uri]));
119+
}
101120
}

apps/valor-software-site/src/app/app.module.ts

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,33 +3,41 @@ import { BrowserModule } from '@angular/platform-browser';
33
import { RouterModule } from '@angular/router';
44
import { routes } from './app.routing';
55
import { AppComponent } from './app.component';
6-
import { CommonDocsModule, ARTICLES_LIST, SeoService, PORTFOLIO_LIST, ARTICLES_REFACTORED_TITLE_LIST, OLD_ROUTES_FROM_OLD_SITE } from '@valor-software/common-docs';
6+
import {
7+
ARTICLES_LIST,
8+
ARTICLES_REFACTORED_TITLE_LIST,
9+
CommonDocsModule,
10+
OLD_ROUTES_FROM_OLD_SITE,
11+
PORTFOLIO_LIST,
12+
SeoService
13+
} from '@valor-software/common-docs';
714
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
815
import { ScullyLibModule } from '@scullyio/ng-lib';
9-
import { articlesList, articlesRefactoringTitlesList } from "../../../../assets/articles/articlesList";
10-
import { projectsList } from "../assets/portfolio/portfolio.list";
11-
import { linksFromOldSite } from "../../../../assets/articles/brokenRoutes";
16+
import { articlesList, articlesRefactoringTitlesList } from '../../../../assets/articles/articlesList';
17+
import { projectsList } from '../assets/portfolio/portfolio.list';
18+
import { linksFromOldSite } from '../../../../assets/articles/brokenRoutes';
1219
import { NotFoundComponent } from './404.component';
1320

1421
@NgModule({
15-
declarations: [
16-
AppComponent,
17-
NotFoundComponent
18-
],
19-
imports: [
20-
BrowserModule,
21-
RouterModule.forRoot(routes, {anchorScrolling: 'enabled', onSameUrlNavigation: 'reload'}),
22-
CommonDocsModule,
23-
BrowserAnimationsModule,
24-
ScullyLibModule
25-
],
26-
providers: [
27-
SeoService,
28-
{ provide: ARTICLES_LIST, useValue: articlesList },
29-
{ provide: ARTICLES_REFACTORED_TITLE_LIST, useValue: articlesRefactoringTitlesList },
30-
{ provide: PORTFOLIO_LIST, useValue: projectsList },
31-
{ provide: OLD_ROUTES_FROM_OLD_SITE, useValue: linksFromOldSite },
32-
],
33-
bootstrap: [AppComponent],
22+
declarations: [
23+
AppComponent,
24+
NotFoundComponent
25+
],
26+
imports: [
27+
BrowserModule,
28+
RouterModule.forRoot(routes, { anchorScrolling: 'enabled', onSameUrlNavigation: 'reload' }),
29+
CommonDocsModule,
30+
BrowserAnimationsModule,
31+
ScullyLibModule,
32+
],
33+
providers: [
34+
SeoService,
35+
{ provide: ARTICLES_LIST, useValue: articlesList },
36+
{ provide: ARTICLES_REFACTORED_TITLE_LIST, useValue: articlesRefactoringTitlesList },
37+
{ provide: PORTFOLIO_LIST, useValue: projectsList },
38+
{ provide: OLD_ROUTES_FROM_OLD_SITE, useValue: linksFromOldSite },
39+
],
40+
bootstrap: [AppComponent],
3441
})
35-
export class AppModule {}
42+
export class AppModule {
43+
}

apps/valor-software-site/src/assets/styles/styles.scss

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,4 +99,38 @@ pre, code {
9999

100100
.link {
101101
color: #E24E63;
102+
}
103+
104+
.cookie-banner {
105+
opacity: 1;
106+
transition: opacity 1s ease;
107+
position: fixed;
108+
bottom: 0;
109+
left: 0;
110+
right: 0;
111+
z-index: 10000;
112+
width: 100%;
113+
background: #252829;
114+
color: #ffffff;
115+
overflow: hidden;
116+
box-sizing: border-box;
117+
line-height: 24px;
118+
display: flex !important;
119+
flex-wrap: nowrap;
120+
justify-content: space-between;
121+
padding: 16px 109px;
122+
align-items: center;
123+
124+
.accept-btn {
125+
display: block;
126+
padding: 0.4em 0.8em;
127+
font-size: .9em;
128+
font-weight: 700;
129+
border-width: 2px;
130+
border-style: solid;
131+
text-align: center;
132+
white-space: nowrap;
133+
border-color: transparent;
134+
color: #E3E3E3FF
135+
}
102136
}

0 commit comments

Comments
 (0)