Skip to content

Commit f4982ec

Browse files
authored
Cookie banner (#529)
* Add Cookie Banner (#529) Signed-off-by: Aleksandr Muravja <alex@kyberorg.io>
1 parent fac1254 commit f4982ec

32 files changed

Lines changed: 1096 additions & 12 deletions

frontend/css/main_view.css

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,11 @@ vaadin-tab a iron-icon {
3434
.subtitle-tab {
3535
color: rgba(24, 39, 57, 0.94);
3636
}
37+
38+
.cb-dialog {
39+
max-width: 100%;
40+
}
41+
42+
.cb-button {
43+
margin-right: 1rem;
44+
}

pom.xml

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -220,21 +220,28 @@
220220
<artifactId>hibernate-validator</artifactId>
221221
<version>6.2.0.Final</version>
222222
</dependency>
223-
223+
224224
<!-- Sitemap generator -->
225225
<dependency>
226226
<groupId>com.github.dfabulich</groupId>
227227
<artifactId>sitemapgen4j</artifactId>
228228
<version>1.1.2</version>
229229
</dependency>
230-
230+
231+
<!-- Toggle (On-Off) Button Component -->
232+
<dependency>
233+
<groupId>com.vaadin.componentfactory</groupId>
234+
<artifactId>togglebutton</artifactId>
235+
<version>1.0.1</version>
236+
</dependency>
237+
231238
<!-- Spring Annotation Processor for generating .properties metadata -->
232239
<dependency>
233240
<groupId>org.springframework.boot</groupId>
234241
<artifactId>spring-boot-configuration-processor</artifactId>
235242
<optional>true</optional>
236243
</dependency>
237-
244+
238245
<!-- Spring Boot Tests -->
239246
<dependency>
240247
<groupId>org.springframework.boot</groupId>

src/main/java/io/kyberorg/yalsee/constants/App.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
* @since 2.3
77
*/
88
public final class App {
9-
109
public static final String EQUAL = "=";
1110
public static final String AND = "&";
1211
public static final String AT = "@";
@@ -15,6 +14,7 @@ public final class App {
1514
public static final String NEW_LINE = System.getProperty("line.separator");
1615
public static final String WEB_NEW_LINE = "<BR>";
1716
public static final String URL_SAFE_SEPARATOR = ">>";
17+
public static final int THREE = 3;
1818

1919
private App() {
2020
throw new UnsupportedOperationException("Utility class");
@@ -83,5 +83,7 @@ public static class Api {
8383

8484
public static class Session {
8585
public static final int SESSION_WATCHDOG_INTERVAL_MILLIS = 20000; //20 seconds
86+
public static final String COOKIE_BANNER_ALREADY_SHOWN = "COOKIE_BANNER_ALREADY_SHOWN";
87+
public static final String COOKIE_BANNER_ANALYTICS_ALLOWED = "COOKIE_BANNER_ANALYTICS_ALLOWED";
8688
}
8789
}

src/main/java/io/kyberorg/yalsee/ui/MainView.java

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,14 @@
2828
import com.vaadin.flow.theme.lumo.Lumo;
2929
import io.kyberorg.yalsee.Endpoint;
3030
import io.kyberorg.yalsee.constants.App;
31+
import io.kyberorg.yalsee.ui.components.CookieBanner;
3132
import io.kyberorg.yalsee.ui.dev.AppInfoView;
3233
import io.kyberorg.yalsee.utils.AppUtils;
3334
import io.kyberorg.yalsee.utils.session.SessionBox;
3435

3536
import java.util.HashMap;
3637
import java.util.Map;
38+
import java.util.Objects;
3739

3840
import static io.kyberorg.yalsee.ui.MainView.IDs.APP_LOGO;
3941

@@ -91,12 +93,22 @@ public MainView(final AppUtils appUtils) {
9193

9294
setId(IDs.VIEW_ID);
9395

94-
//session tricks
95-
SessionBox.storeSession(VaadinSession.getCurrent());
96+
//session trick
97+
VaadinSession session = VaadinSession.getCurrent();
98+
SessionBox.storeSession(session);
9699

97100
// hide the splash screen after the main view is loaded
98101
UI.getCurrent().getPage().executeJs(
99102
"document.querySelector('#splash-screen').classList.add('loaded')");
103+
104+
//Cookie Banner
105+
readAndWriteCookieBannerRelatedSettingsFromSession(session);
106+
boolean shouldDisplayBanner = !(boolean) session.getAttribute(App.Session.COOKIE_BANNER_ALREADY_SHOWN);
107+
if (shouldDisplayBanner) {
108+
CookieBanner cookieBanner = new CookieBanner();
109+
cookieBanner.getContent().open();
110+
session.setAttribute(App.Session.COOKIE_BANNER_ALREADY_SHOWN, true);
111+
}
100112
}
101113

102114
private void addLogo() {
@@ -174,11 +186,21 @@ public void configurePage(final InitialPageSettings settings) {
174186
settings.addMetaTag("twitter:image", previewImage);
175187

176188
settings.addInlineFromFile("splash-screen.html", InitialPageSettings.WrapMode.NONE);
177-
if (appUtils.isGoogleAnalyticsEnabled()) {
189+
if (appUtils.isGoogleAnalyticsEnabled() && appUtils.isGoogleAnalyticsAllowed(VaadinSession.getCurrent())) {
178190
settings.addInlineFromFile(appUtils.getGoggleAnalyticsFileName(), InitialPageSettings.WrapMode.NONE);
179191
}
180192
}
181193

194+
private void readAndWriteCookieBannerRelatedSettingsFromSession(final VaadinSession session) {
195+
if (Objects.isNull(session.getAttribute(App.Session.COOKIE_BANNER_ALREADY_SHOWN))) {
196+
session.setAttribute(App.Session.COOKIE_BANNER_ALREADY_SHOWN, false);
197+
}
198+
199+
if (Objects.isNull(session.getAttribute(App.Session.COOKIE_BANNER_ANALYTICS_ALLOWED))) {
200+
session.setAttribute(App.Session.COOKIE_BANNER_ANALYTICS_ALLOWED, false);
201+
}
202+
}
203+
182204
public static class IDs {
183205
public static final String VIEW_ID = "mainView";
184206
public static final String APP_LOGO = "appLogo";
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
package io.kyberorg.yalsee.ui.components;
2+
3+
import com.vaadin.flow.component.ClickEvent;
4+
import com.vaadin.flow.component.Composite;
5+
import com.vaadin.flow.component.Tag;
6+
import com.vaadin.flow.component.button.Button;
7+
import com.vaadin.flow.component.button.ButtonVariant;
8+
import com.vaadin.flow.component.checkbox.Checkbox;
9+
import com.vaadin.flow.component.dialog.Dialog;
10+
import com.vaadin.flow.component.html.Anchor;
11+
import com.vaadin.flow.component.html.H3;
12+
import com.vaadin.flow.component.html.Span;
13+
import com.vaadin.flow.component.orderedlayout.FlexLayout;
14+
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
15+
import io.kyberorg.yalsee.Endpoint;
16+
import io.kyberorg.yalsee.constants.App;
17+
import lombok.extern.slf4j.Slf4j;
18+
19+
import java.util.stream.Stream;
20+
21+
/**
22+
* Cookie Banner.
23+
*
24+
* @since 3.5
25+
*/
26+
@Slf4j
27+
@Tag("yalsee-cookie-banner")
28+
public class CookieBanner extends Composite<Dialog> {
29+
public static final String TAG = "[" + CookieBanner.class.getSimpleName() + "]";
30+
31+
private final VerticalLayout coreLayout = new VerticalLayout();
32+
private final H3 title = new H3("Cookies Info");
33+
34+
private final Span textAndLinkSection = new Span();
35+
private final Span text = new Span("This website uses cookies, because without cookies it works like shit. ");
36+
private final Anchor link = new Anchor(Endpoint.UI.APP_INFO_PAGE, "More info");
37+
38+
private final FlexLayout buttons = new FlexLayout();
39+
private final Button onlyNecessaryButton = new Button("Only necessary cookies");
40+
private final Button mySelectionButton = new Button("Allow selection");
41+
private final Button allowAllButton = new Button("Allow all cookies");
42+
43+
private final FlexLayout checkboxes = new FlexLayout();
44+
private final Checkbox onlyNecessaryBox = new Checkbox();
45+
private final Checkbox analyticsBox = new Checkbox();
46+
47+
/**
48+
* Creates Cookie Banner.
49+
*/
50+
public CookieBanner() {
51+
init();
52+
applyStyle();
53+
}
54+
55+
private void init() {
56+
setId(IDs.CB_DIALOG);
57+
58+
title.setId(IDs.CB_TITLE);
59+
textAndLinkSection.setId(IDs.CB_TEXT_AND_LINK_SECTION);
60+
text.setId(IDs.CB_TEXT);
61+
link.setId(IDs.CB_LINK);
62+
63+
checkboxes.setId(IDs.CB_BOXES_SECTION);
64+
65+
onlyNecessaryBox.setId(IDs.CB_ONLY_NECESSARY_BOX);
66+
onlyNecessaryBox.setLabel("Technical");
67+
onlyNecessaryBox.setValue(true);
68+
onlyNecessaryBox.setEnabled(false);
69+
onlyNecessaryBox.setReadOnly(true);
70+
71+
analyticsBox.setId(IDs.CB_ANALYTICS_BOX);
72+
analyticsBox.setLabel("Analytics");
73+
74+
buttons.setId(IDs.CB_BUTTONS_SECTION);
75+
76+
onlyNecessaryButton.setId(IDs.CB_ONLY_NECESSARY_BUTTON);
77+
onlyNecessaryButton.addClickListener(this::onNecessaryButtonClicked);
78+
onlyNecessaryButton.addThemeVariants(ButtonVariant.LUMO_SMALL, ButtonVariant.LUMO_PRIMARY,
79+
ButtonVariant.LUMO_CONTRAST);
80+
81+
mySelectionButton.setId(IDs.CB_SELECTION_BUTTON);
82+
mySelectionButton.addClickListener(this::onMySelectionButtonClicked);
83+
mySelectionButton.addThemeVariants(ButtonVariant.LUMO_SMALL, ButtonVariant.LUMO_PRIMARY);
84+
85+
allowAllButton.setId(IDs.CB_ALLOW_ALL_BUTTON);
86+
allowAllButton.addClickListener(this::onAllowAllButtonClicked);
87+
allowAllButton.addThemeVariants(ButtonVariant.LUMO_SMALL, ButtonVariant.LUMO_PRIMARY);
88+
89+
textAndLinkSection.add(text, link);
90+
checkboxes.add(onlyNecessaryBox, analyticsBox);
91+
buttons.add(onlyNecessaryButton, mySelectionButton, allowAllButton);
92+
93+
coreLayout.add(title, textAndLinkSection, checkboxes, buttons);
94+
95+
getContent().add(coreLayout);
96+
}
97+
98+
private void applyStyle() {
99+
Stream<Checkbox> boxStream = Stream.of(onlyNecessaryBox, analyticsBox);
100+
boxStream.forEach(box -> box.addClassName(Classes.CB_BOX));
101+
102+
buttons.setFlexWrap(FlexLayout.FlexWrap.WRAP);
103+
Stream<Button> buttonStream = Stream.of(onlyNecessaryButton, mySelectionButton, allowAllButton);
104+
buttonStream.forEach(button -> button.addClassName(Classes.CB_BUTTON));
105+
}
106+
107+
private void onNecessaryButtonClicked(final ClickEvent<Button> event) {
108+
// disable others options first
109+
analyticsBox.setValue(false);
110+
writeValuesToSessionAndClose();
111+
}
112+
113+
private void onMySelectionButtonClicked(final ClickEvent<Button> event) {
114+
writeValuesToSessionAndClose();
115+
}
116+
117+
private void onAllowAllButtonClicked(final ClickEvent<Button> event) {
118+
analyticsBox.setValue(true);
119+
writeValuesToSessionAndClose();
120+
}
121+
122+
private void writeValuesToSessionAndClose() {
123+
if (getContent().getUI().isPresent()) {
124+
final boolean uiHasSession = getContent().getUI().get().getSession() != null;
125+
if (uiHasSession) {
126+
final boolean isAnalyticsAllowed = analyticsBox.getValue();
127+
getContent().getUI().get().getSession()
128+
.setAttribute(App.Session.COOKIE_BANNER_ANALYTICS_ALLOWED, isAnalyticsAllowed);
129+
} else {
130+
log.warn("{} UI has no session inside. Skipping further actions...", TAG);
131+
}
132+
} else {
133+
log.warn("{} UI is missing. Skipping further actions...", TAG);
134+
}
135+
getContent().close();
136+
}
137+
138+
public static final class IDs {
139+
public static final String CB_DIALOG = "cbDialog";
140+
public static final String CB_TITLE = "cbTitle";
141+
public static final String CB_TEXT_AND_LINK_SECTION = "cbTextAndLinkSection";
142+
public static final String CB_TEXT = "cbText";
143+
public static final String CB_LINK = "cbLink";
144+
public static final String CB_BOXES_SECTION = "cbBoxes";
145+
public static final String CB_ONLY_NECESSARY_BOX = "cbOnlyNecessaryBox";
146+
public static final String CB_ANALYTICS_BOX = "cbAnalyticsBox";
147+
public static final String CB_BUTTONS_SECTION = "cbButtons";
148+
public static final String CB_ONLY_NECESSARY_BUTTON = "cbOnlyNecessaryButton";
149+
public static final String CB_SELECTION_BUTTON = "cbSelectionButton";
150+
public static final String CB_ALLOW_ALL_BUTTON = "cbAllowAllButton";
151+
}
152+
153+
public static final class Classes {
154+
public static final String CB_BUTTON = "cb-button";
155+
public static final String CB_BOX = "cb-box";
156+
}
157+
}

src/main/java/io/kyberorg/yalsee/ui/dev/AppInfoView.java

Lines changed: 73 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
package io.kyberorg.yalsee.ui.dev;
22

3-
import com.vaadin.flow.component.html.Anchor;
4-
import com.vaadin.flow.component.html.H3;
5-
import com.vaadin.flow.component.html.Span;
3+
import com.vaadin.componentfactory.ToggleButton;
4+
import com.vaadin.flow.component.html.*;
65
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
76
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
87
import com.vaadin.flow.router.PageTitle;
98
import com.vaadin.flow.router.Route;
9+
import com.vaadin.flow.server.VaadinSession;
1010
import com.vaadin.flow.server.Version;
1111
import com.vaadin.flow.spring.annotation.SpringComponent;
1212
import com.vaadin.flow.spring.annotation.UIScope;
@@ -54,7 +54,8 @@ private void init() {
5454
setId(IDs.VIEW_ID);
5555

5656
VerticalLayout publicInfoArea = publicInfoArea();
57-
add(publicInfoArea);
57+
VerticalLayout cookieArea = cookieArea();
58+
add(publicInfoArea, cookieArea);
5859

5960
if (appUtils.isDevelopmentModeActivated() || appUtils.hasDevHeader()) {
6061
VerticalLayout devInfoArea = devInfoArea();
@@ -97,6 +98,62 @@ private VerticalLayout publicInfoArea() {
9798
return publicArea;
9899
}
99100

101+
private VerticalLayout cookieArea() {
102+
VerticalLayout cookieArea = new VerticalLayout();
103+
cookieArea.setId(IDs.COOKIE_AREA);
104+
H4 title = new H4("About Cookies");
105+
title.setId(IDs.COOKIE_TITLE);
106+
107+
Span cookieText = new Span();
108+
cookieText.setId(IDs.COOKIE_TEXT_SPAN);
109+
110+
Span textStart = new Span("Yalsee is using ");
111+
Anchor link = new Anchor("https://www.cookiesandyou.com/", "Cookies");
112+
link.setId(IDs.COOKIE_LINK);
113+
114+
Span textEnd = new Span(" to make this site works. ");
115+
116+
Span techDetailsText = new Span("There are technical cookies like JSESSION, "
117+
+ "what keeps session and preferences "
118+
+ "and analytics cookies (Google Analytics) used for collecting usage statistics.");
119+
techDetailsText.setId(IDs.COOKIE_TECH_DETAILS);
120+
121+
H5 cookieCurrentSettingsSubTitle = new H5("Current Settings");
122+
cookieCurrentSettingsSubTitle.setId(IDs.COOKIE_CURRENT_SETTINGS_TITLE);
123+
124+
Span techCookies = new Span();
125+
techCookies.setId(IDs.TECH_COOKIE_SPAN);
126+
127+
Span techCookiesLabel = new Span("Technical cookies: ");
128+
techCookiesLabel.setId(IDs.TECH_COOKIE_LABEL);
129+
130+
ToggleButton techCookiesValue = new ToggleButton(true);
131+
techCookiesValue.setId(IDs.TECH_COOKIE_VALUE);
132+
techCookiesValue.setEnabled(false);
133+
134+
Span analyticsCookies = new Span();
135+
analyticsCookies.setId(IDs.ANALYTICS_COOKIE_SPAN);
136+
137+
Span analyticsCookiesLabel = new Span("Analytics cookies: ");
138+
analyticsCookiesLabel.setId(IDs.ANALYTICS_COOKIE_LABEL);
139+
140+
ToggleButton analyticsCookiesValue = new ToggleButton();
141+
analyticsCookiesValue.setId(IDs.ANALYTICS_COOKIE_VALUE);
142+
analyticsCookiesValue.setValue(appUtils.isGoogleAnalyticsAllowed(VaadinSession.getCurrent()));
143+
analyticsCookiesValue.addValueChangeListener(event -> {
144+
VaadinSession session = VaadinSession.getCurrent();
145+
if (session != null) {
146+
session.setAttribute(App.Session.COOKIE_BANNER_ANALYTICS_ALLOWED, event.getValue());
147+
}
148+
});
149+
150+
cookieText.add(textStart, link, textEnd, techDetailsText);
151+
techCookies.add(techCookiesLabel, techCookiesValue);
152+
analyticsCookies.add(analyticsCookiesLabel, analyticsCookiesValue);
153+
cookieArea.add(title, cookieText, cookieCurrentSettingsSubTitle, techCookies, analyticsCookies);
154+
return cookieArea;
155+
}
156+
100157
private VerticalLayout devInfoArea() {
101158
VerticalLayout devInfoArea = new VerticalLayout();
102159
devInfoArea.setId(IDs.DEV_INFO_AREA);
@@ -127,5 +184,17 @@ public static class IDs {
127184
public static final String COMMIT_LINK = "commitLink";
128185
public static final String DEV_INFO_AREA = "devInfoArea";
129186
public static final String GOOGLE_ANALYTICS_BANNER = "googleAnalyticsBanner";
187+
public static final String COOKIE_AREA = "cookieArea";
188+
public static final String COOKIE_TITLE = "cookieTitle";
189+
public static final String COOKIE_TEXT_SPAN = "cookieTextSpan";
190+
public static final String COOKIE_LINK = "cookieLink";
191+
public static final String COOKIE_TECH_DETAILS = "cookieTechDetails";
192+
public static final String COOKIE_CURRENT_SETTINGS_TITLE = "cookieCurrentSettingsTitle";
193+
public static final String TECH_COOKIE_SPAN = "techCookieSpan";
194+
public static final String TECH_COOKIE_LABEL = "techCookieLabel";
195+
public static final String TECH_COOKIE_VALUE = "techCookieValue";
196+
public static final String ANALYTICS_COOKIE_SPAN = "analyticsCookieSpan";
197+
public static final String ANALYTICS_COOKIE_LABEL = "analyticsCookieLabel";
198+
public static final String ANALYTICS_COOKIE_VALUE = "analyticsCookieValue";
130199
}
131200
}

0 commit comments

Comments
 (0)