Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions content/_data/constants.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ header_nav:
- title: Search
page: pages/search.md

cookie_consent:
title: We Use Cookies
chapters:
- text: |-
We value your privacy. The FiQCI webpage uses functional cookies and anonymous analytics cookies.
By clicking "Accept", you consent to the use of these cookies.
- text: |-
The data collected is non-identifiable and cannot be traced back to you.

#Following are constants per page. Can be accessed with page.url
"/":
hero:
Expand Down Expand Up @@ -233,3 +242,93 @@ header_nav:
basis: "PRX, CZ"
topology: "Square grid"
device_id: "Q50"

"/cookies/":
title: Cookie Policy
desc: |-
This is the cookie policy for FiQCI, accessible from https://fiqci.fi/cookies/

general:
- When you visit our website, cookies will save information about your stay. Cookies are small text files that are placed on your device.
There are two main types of cookies; session cookies and persistent cookies. Session cookies will be removed from your device as soon
as you close your browser. Persistent cookies will remain stored until they are deleted or expire.

- The data collected by the cookies is used to track the trends in our site usage. No personal data is collected.
This way, we can improve the usability as well as identify the most interesting areas of our website.

- Cookies help us get an overview of your visit to our website so we can get an idea of how many visitors we have, which pages are visited,
and how long visitors stay on our website. We use this information to improve our website and make it more user-friendly.
All data collected is anonymous and cannot be used to identify you as an individual.

- The FiQCI webpages uses anonymous cookies to collect data about user behavior through Matomo.
This information helps us improve our services and provide a better user experience by telling us how users use the site.
The data collected is non-identifiable and cannot be traced back to you.

- Additionally we use mandatory functional cookies to ensure the website functions properly.
These cookies do not collect any information about you and only help the website function.
They are only valid for the current session and are deleted when you close your browser.

delete:
title: How to Delete Cookies
desc: |-
The cookies can be deleted by clearing your browser's cache and cookies. You can also manage your cookie preferences through
your browser settings. You can also simply refuse cookie consent through the cookie consent banner that appears when you first visit our website.

change:
title: How to Change Consent
desc: |-
If you have already accepted/declined cookies, you can change your preferences at any time by clicking below.

lifetime:
title: Cookie Lifetime
desc: |-
The length of time a cookie is stored on your devices and browsers varies.The lifetime is calculated according to your last visit
to the website. When a cookie expires, it is automatically deleted. All our cookies' lifetimes are specified in our cookie policy.

cookies:
title: Cookies Used
types:
- name: "Functional Cookies"
desc: |-
These cookies are necessary for the website to function properly.
lifetime: Session
- name: "Analytics Cookies"
desc: |-
These cookies help us understand how visitors interact with our website by collecting and reporting information anonymously.
lifetime: 1 year

"/accessibility/":
title: Accessibility Statement
desc: |-
This is the accessibility statement for FiQCI, accessible from https://fiqci.fi/accessibility/.
Updated on 19.6.2025

general:
- The FiQCI website is designed to be accessible to all users.
We strive to comply with the Web Content Accessibility Guidelines (WCAG) 2.1 Level AA standards.
If you encounter any accessibility issues while using our website, please contact us at

status:
title: Compliance Status
desc: |-
Meets all critical accessibility requirements.

complaint:
title: Reporting Accessibility Issues
desc: |-
If you notice accessibility problems on the website, start by giving feedback to us, that is,
the website administrator. Receiving a response may take 14 days. If you are not satisfied with
the response from us or if you do not receive any response within two weeks, you may file a report
with the
link:
title: Regional State Administrative Agency for Southern Finland
href: https://www.saavutettavuusvaatimukset.fi/en/user-rights/submit-complaint-web-accessibility-or-request-clarification

contact:
title: Supervisory Authority Contact Information
info:
name: Liikenne- ja viestintävirasto Traficom
division: Digitaalisen esteettömyyden ja saavutettavuuden valvontayksikkö
web: www.saavutettavuusvaatimukset.fi
email: saavutettavuus@traficom.fi
phone: 029 534 5000
2 changes: 1 addition & 1 deletion content/_layouts/home.html
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ <h2 id="about-fiqci" class="">FiQCI Mission</h2>
<div class="flex md:hidden">
<div class="flex-auto mt-8 pt-12">
<div class="mb-6 ">
<h2 id="about-fiqci" class="">FiQCI Mission</h2>
<h2 id="about-fiqci-mobile" class="">FiQCI Mission</h2>
</div>
<div class="w-full pb-4 md:pb-0 md:min-w-64">
<section class="bg-[#0066CC] text-white text-left font-bold text-lg py-8 px-2 sm:px-8">
Expand Down
2 changes: 1 addition & 1 deletion content/_layouts/post.html
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ <h2 class="text-[1.5em] font-[700]" >Give feedback!</h2>
{%- include react/root.html id='references-accordion' -%}
</div>
</div>
<div class='lg:block col-span-full lg:col-span-2 2xl:col-span-1 xl:w-full xl:max-w-screen max-w-[740px] overflow-y-scroll h-full scroll-smooth' style='height:fit-content;'>
<div class='lg:block col-span-full lg:col-span-2 2xl:col-span-1 xl:w-full xl:max-w-screen max-w-[740px] lg:!overflow-hidden h-full scroll-smooth' style='height:fit-content;'>
{%- include react/root.html id='read-next' -%}
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion content/pages/about.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ react: true

{% assign about_data = site.data.constants.["/about/"] %}

<div class="mt-[24px] grid grid-cols-1 lg:grid lg:grid-cols-2 gap-8">
<div class="my-[24px] grid grid-cols-1 lg:grid lg:grid-cols-2 gap-8">
<h1 class="text-3xl text-on-white font-bold col-span-1 lg:col-span-2">About FiQCI</h1>
<div class="col-span-1 lg:mr-10">
<p class="text-on-white">{{ about_data.desc }}</p>
Expand Down
42 changes: 42 additions & 0 deletions content/pages/accessibility.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
layout: page
title: Accessibility Statement
subtitle: Accessibility Statement
react: true
---

{% assign accessibility_data = site.data.constants.["/accessibility/"] %}

<div class="lg:grid lg:grid-cols-5 gap-8">
<div class="col-span-1"></div>
<div class="col-span-3 flex flex-col text-on-white my-[24px]">
<h2 class="mb-4">{{ accessibility_data.title }}</h2>
<p class="mb-4"> {{ accessibility_data.general }} <a class="text-sky-800 hover:underline" href="mailto:{{site.data.constants.feedback_email}}" > {{ site.data.constants.feedback_email }}</a> </p>

<h2 class="mb-4">{{ accessibility_data.status.title }}</h2>
<p class="mb-4"> {{ accessibility_data.status.desc }} </p>

<h2 class="mb-4">{{ accessibility_data.complaint.title }}</h2>
<p class="mb-4"> {{ accessibility_data.complaint.desc }} <a class="text-sky-800 hover:underline" target="_blank" href="{{ accessibility_data.complaint.link.href }}"> {{ accessibility_data.complaint.link.title }} </a> </p>

<h2 class="mb-4">{{ accessibility_data.complaint.contact.title }}</h2>
{% assign contact = accessibility_data.complaint.contact.info %}
{% assign contact_labels = "name:Name,division:Division,web:Website,email:Email,phone:Phone" | split: "," %}
{% for pair in contact_labels %}
{% assign parts = pair | split: ":" %}
{% assign key = parts[0] %}
{% assign label = parts[1] %}
{% if contact[key] %}
<p class="mb-2"><strong>{{ label }}:</strong>
{% if key == "web" %}
<a class="text-sky-800 hover:underline" href="https://{{ contact[key] }}" target="_blank" rel="noopener">{{ contact[key] }}</a>
{% elsif key == "email" %}
<a class="text-sky-800 hover:underline" href="mailto:{{ contact[key] }}">{{ contact[key] }}</a>
{% else %}
{{ contact[key] }}
{% endif %}
</p>
{% endif %}
{% endfor %}
</div>
</div>
46 changes: 46 additions & 0 deletions content/pages/cookies.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
layout: page
title: Cookie Policy
subtitle: Cookie policy
react: true
---

{% assign cookie_data = site.data.constants.["/cookies/"] %}

<div class="lg:grid lg:grid-cols-5 gap-8">
<div class="col-span-1"></div>
<div class="col-span-3 flex flex-col text-on-white my-[24px]">
<h2 class="mb-4">{{ cookie_data.title }}</h2>
{% for i in cookie_data.general %}
<p class="mb-4">{{ i }}</p>
{% endfor %}

<h2 class="mb-4">{{ cookie_data.delete.title }}</h2>
<p class="mb-4">{{ cookie_data.delete.desc }}</p>

<h2 class="mb-4">{{ cookie_data.change.title }}</h2>
<p class="mb-0">{{ cookie_data.change.desc }}</p>
<div class="mb-4" >
{%- include react/root.html id='open-cookie-modal' -%}
</div>

<h2 class="mb-4">{{ cookie_data.lifetime.title }}</h2>
<p class="mb-4">{{ cookie_data.lifetime.desc }}</p>

<h2 class="mb-4">{{ cookie_data.cookies.title }}</h2>

{% for i in cookie_data.cookies.types %}
<h3 class="font-bold text-[18px]">{{ i.name }}</h3>
<div class="list-disc ml-8">
<div class="flex gap-2">
<p class="font-bold">Description: </p>
<p > {{ i.desc }}</p>
</div>
<div class="flex gap-2">
<p class="font-bold">Lifetime: </p>
<p > {{ i.lifetime }}</p>
</div>
</div>
{% endfor %}
</div>
</div>
171 changes: 171 additions & 0 deletions src/components/CookieConsent.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import React, { useEffect, useState } from 'react'

import { CModal, CCard, CCardTitle, CCardContent, CButton } from '@cscfi/csc-ui-react'

export const CookieModal = props => {
const [isModalOpen, setIsModalOpen] = useState(false);

const chapters = props.chapters || [];

useEffect(() => {
const cookieConsent = document.cookie
.split('; ')
.find(row => row.startsWith('cookie_consent='));

if (!cookieConsent) {
setIsModalOpen(true);
return;
}

const consentValue = cookieConsent.split('=')[1];
if (consentValue === 'true' || consentValue === 'false') {
setIsModalOpen(false);
} else {
setIsModalOpen(true);
}
}, []);

// Unified close handler
const closeModal = () => setIsModalOpen(false);

const clickOutside = () => {
if (isModalOpen) {
closeModal();
const expiryDate = new Date();
expiryDate.setFullYear(expiryDate.getFullYear() + 1);
document.cookie = `cookie_consent=false; path=/; expires=${expiryDate.toUTCString()}`;
}
}

const handleAcceptCookies = () => {
closeModal();
const expiryDate = new Date();
expiryDate.setFullYear(expiryDate.getFullYear() + 1);
document.cookie = `cookie_consent=true; path=/; expires=${expiryDate.toUTCString()}`;
}
const handleDeclineCookies = () => {
closeModal();
const cookies = document.cookie.split(';');
for (const cookie of cookies) {
const eqPos = cookie.indexOf('=');
const name = eqPos > -1 ? cookie.substr(0, eqPos).trim() : cookie.trim();
// Only delete cookies for the rahtiapp domain
if (name.endsWith('.rahtiapp.fi')) {
document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain=.rahtiapp.fi`;
}
}
const expiryDate = new Date();
expiryDate.setFullYear(expiryDate.getFullYear() + 1);
document.cookie = `cookie_consent=false; path=/; expires=${expiryDate.toUTCString()}`;
}

return (
<CModal
key={isModalOpen ? 'open' : 'closed'}
style={{ 'overflow': 'scroll' }}
className='!overflow-hidden'
value={isModalOpen}
dismissable
onChangeValue={() => clickOutside()}
>
<CCard style={{ 'overflow': 'scroll' }} className='overflow-scroll max-h-[85vh]'>
<CCardTitle className='text-on-white font-bold' >Cookie consent</CCardTitle>
<CCardContent className='p-8'>
<h3 className='text-on-white text-[32px]'>{props.title}</h3>
{chapters.map((chapter, index) => (
<div key={index}>
<p className='text-on-white'>{chapter.text}</p>
</div>
))}

<a className='text-sky-800 hover:underline' href="/cookies">Cookie policy</a>
<div className='flex flex-col md:flex-row justify-around gap-4'>
<CButton
className='grow text-white bg-[#0D2B53] hover:bg-blue-600'
no-radius
size="large"
onClick={handleDeclineCookies} text>Decline
</CButton>
<CButton
className='grow text-white bg-[#0D2B53] hover:bg-blue-600'
no-radius
size="large"
onClick={handleAcceptCookies} text>Accept
</CButton>
</div>
</CCardContent>
</CCard>
</CModal>
);
};

export const CookieModalManual = props => {
const [isModalOpen, setIsModalOpen] = useState(false);
const closeModal = () => setIsModalOpen(false);

const chapters = props.chapters || [];

const handleAcceptCookies = () => {
closeModal();
const expiryDate = new Date();
expiryDate.setFullYear(expiryDate.getFullYear() + 1);
document.cookie = `cookie_consent=true; path=/; expires=${expiryDate.toUTCString()}`;
}
const handleDeclineCookies = () => {
closeModal();
const cookies = document.cookie.split(';');
for (const cookie of cookies) {
const eqPos = cookie.indexOf('=');
const name = eqPos > -1 ? cookie.substr(0, eqPos).trim() : cookie.trim();
// Only delete cookies for the rahtiapp domain
if (name.endsWith('.rahtiapp.fi')) {
document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain=.rahtiapp.fi`;
}
}
const expiryDate = new Date();
expiryDate.setFullYear(expiryDate.getFullYear() + 1);
document.cookie = `cookie_consent=false; path=/; expires=${expiryDate.toUTCString()}`;
}

return (
<div>
<a className='text-sky-800 hover:underline' onClick={() => setIsModalOpen(true)}>Open Cookie Consent popup</a>
<CModal
key={isModalOpen ? 'open' : 'closed'}
style={{ 'overflow': 'scroll' }}
className='!overflow-hidden'
value={isModalOpen}
dismissable
onChangeValue={() => closeModal()}
>
<CCard style={{ 'overflow': 'scroll' }} className='overflow-scroll scroll-smooth max-h-[85vh]'>
<CCardTitle className='text-on-white font-bold' >Cookie consent</CCardTitle>
<CCardContent className='p-8'>
<h3 className='text-on-white text-[32px]'>{props.title}</h3>
{chapters.map((chapter, index) => (
<div key={index}>
<p className='text-on-white'>{chapter.text}</p>
</div>
))}
<a className='text-sky-800 hover:underline' href="/cookies">Cookie policy</a>
<div className='flex flex-col md:flex-row justify-around gap-4'>
<CButton
className='grow text-white bg-[#0D2B53] hover:bg-blue-600'
no-radius
size="large"
onClick={handleDeclineCookies} text>Decline
</CButton>
<CButton
className='grow text-white bg-[#0D2B53] hover:bg-blue-600'
no-radius
size="large"
onClick={handleAcceptCookies} text>Accept
</CButton>
</div>
</CCardContent>
</CCard>
</CModal>
</div>

);
};
Loading
Loading