Browse Source

fix: Reformat whole project.

master
Denis Thiessen 6 months ago
parent
commit
9b5cae0d53
  1. 211
      src/App.js
  2. 6
      src/App.test.js
  3. 57
      src/components/DownloadButton.jsx
  4. 30
      src/components/InfoPageComponent.jsx
  5. 35
      src/components/RandomIDComponent.jsx
  6. 125
      src/components/questionnaire/QuestionComponent.jsx
  7. 66
      src/components/questionnaire/QuestionData.jsx
  8. 44
      src/components/questionnaire/QuestionnaireData.jsx
  9. 99
      src/components/webpage_container/StudySite.jsx
  10. 29
      src/components/webpage_container/WebpageBanner.jsx
  11. 63
      src/core/audio/AudioHandler.jsx
  12. 30
      src/core/audio/ChordHelper.jsx
  13. 2
      src/core/i18n/I18NHandler.jsx
  14. 10
      src/core/i18n/i18n.js
  15. 6
      src/core/log/RouteTracker.jsx
  16. 102
      src/core/log/SensorLogger.jsx
  17. 23
      src/index.js
  18. 16
      src/interfaces/DataComponent.jsx
  19. 6
      src/interfaces/ResetableComponent.jsx
  20. 7
      src/pages/NoPageFound.jsx
  21. 10
      src/pages/TestEndPage.jsx
  22. 22
      src/pages/TestInfoPage.jsx
  23. 20
      src/pages/TestInfoPage2.jsx
  24. 32
      src/pages/TestPage.jsx
  25. 31
      src/pages/TestPage2.jsx
  26. 40
      src/pages/TestQuestionnaire.jsx
  27. 212
      src/pages/study_site_1/StartPage1.jsx

211
src/App.js

@ -1,9 +1,9 @@
import React from "react";
import { Route, Routes } from "react-router-dom";
import { GeistProvider, CssBaseline } from '@geist-ui/core'
import { GeistProvider, CssBaseline } from "@geist-ui/core";
import RouteTracker from "./core/log/RouteTracker";
import { createStore } from 'zustand/vanilla'
import { persist, createJSONStorage } from 'zustand/middleware'
import { createStore } from "zustand/vanilla";
import { persist, createJSONStorage } from "zustand/middleware";
import { PARTICIPANT_NUMBER } from "./core/Constants";
import { StudySite } from "./components/webpage_container/StudySite";
@ -15,20 +15,25 @@ const TestInfoPage2 = React.lazy(() => import("./pages/TestInfoPage2"));
const TestEndPage = React.lazy(() => import("./pages/TestEndPage"));
const StartPage1 = React.lazy(() => import("./pages/study_site_1/StartPage1"));
export const sensorLogState = createStore(
persist(
() => ({
sensorLog: {mouseLog: [], clickedElements: [], visitedSites: [], playedSonifications: []}
sensorLog: {
mouseLog: [],
clickedElements: [],
visitedSites: [],
playedSonifications: [],
},
}),
{
name: 'sensor-storage', // name of the item in the storage (must be unique)
name: "sensor-storage", // name of the item in the storage (must be unique)
storage: createJSONStorage(() => sessionStorage), // (optional) by default, 'localStorage' is used
},
),
)
}
)
);
export const { getState, setState, subscribe, getInitialState } = sensorLogState;
export const { getState, setState, subscribe, getInitialState } =
sensorLogState;
const latinSquare = [
[1, 2, 3, 4, 5, 6],
@ -36,46 +41,51 @@ const latinSquare = [
[3, 4, 5, 1, 6, 2],
[4, 6, 1, 2, 3, 5],
[2, 3, 6, 5, 4, 1],
[5, 1, 4, 6, 2, 3]
[5, 1, 4, 6, 2, 3],
];
const getLatinSquareIndex = function(participantNumber) {
return ((participantNumber - 1) % latinSquare.length);
const getLatinSquareIndex = function (participantNumber) {
return (participantNumber - 1) % latinSquare.length;
};
const findIndexOfOrderElement = function(el, order) {
for(var i = 0; i < order.length; i++) {
if(order[i] === el) {
const findIndexOfOrderElement = function (el, order) {
for (var i = 0; i < order.length; i++) {
if (order[i] === el) {
return i;
}
}
};
const getLatinSquareOrder = function(participantNumber) {
const getLatinSquareOrder = function (participantNumber) {
const latinSquareIndex = getLatinSquareIndex(participantNumber);
const latinSquareElement = latinSquare[latinSquareIndex];
const originalOrder = [1, 2, 3, 4, 5, 6];
var latinSquareRedirectOrder = [];
latinSquareRedirectOrder.push(("in-between-" + latinSquareElement[0]));
for(var originalOrderElement of originalOrder) {
const indexElement = findIndexOfOrderElement(originalOrderElement, latinSquareElement);
if(indexElement === originalOrder.length - 1) {
latinSquareRedirectOrder.push("in-between-" + latinSquareElement[0]);
for (var originalOrderElement of originalOrder) {
const indexElement = findIndexOfOrderElement(
originalOrderElement,
latinSquareElement
);
if (indexElement === originalOrder.length - 1) {
latinSquareRedirectOrder.push("end");
continue;
}
const nextOrderElement = latinSquareElement[(indexElement + 1)];
const nextOrderElementIndex = findIndexOfOrderElement(nextOrderElement, latinSquareElement);
const nextOrderElement = latinSquareElement[indexElement + 1];
const nextOrderElementIndex = findIndexOfOrderElement(
nextOrderElement,
latinSquareElement
);
const nextRedirctElement = latinSquareElement[nextOrderElementIndex];
latinSquareRedirectOrder.push(("in-between-" + nextRedirctElement ));
latinSquareRedirectOrder.push("in-between-" + nextRedirctElement);
}
return latinSquareRedirectOrder;
};
function App() {
// TODO FIX...
/*
There is an weird interaction/error with the lazy loading of the routers
@ -83,12 +93,12 @@ function App() {
lazily... For now this works, but still, this is very hacky and I hate this...
*/
var wait = (ms) => {
const start = Date.now();
let now = start;
while (now - start < ms) {
now = Date.now();
}
}
const start = Date.now();
let now = start;
while (now - start < ms) {
now = Date.now();
}
};
wait(200);
@ -97,23 +107,128 @@ function App() {
return (
<GeistProvider>
<React.Suspense fallback="loading">
<RouteTracker />
<RouteTracker />
<Routes>
<Route path="/" element={<React.Suspense fallback={<>...</>}><TestInfoPage redirectLoc={latinSquareOrder[0]}/></React.Suspense>} />
<Route path="/in-between-1" element={<React.Suspense fallback={<>...</>}><TestInfoPage2 redirectLoc="study-page-1"/></React.Suspense>} />
<Route path="/study-page-1" element={<React.Suspense fallback={<>...</>}><StartPage1 redirectLoc={latinSquareOrder[1]}/></React.Suspense>} />
<Route path="/in-between-2" element={<React.Suspense fallback={<>...</>}><TestInfoPage2 redirectLoc="study-page-2"/></React.Suspense>} />
<Route path="/study-page-2" element={<React.Suspense fallback={<>...</>}><TestPage redirectLoc={latinSquareOrder[2]}/></React.Suspense>} />
<Route path="/in-between-3" element={<React.Suspense fallback={<>...</>}><TestInfoPage2 redirectLoc="study-page-3"/></React.Suspense>} />
<Route path="/study-page-3" element={<React.Suspense fallback={<>...</>}><TestPage redirectLoc={latinSquareOrder[3]}/></React.Suspense>} />
<Route path="/in-between-4" element={<React.Suspense fallback={<>...</>}><TestInfoPage2 redirectLoc="study-page-4"/></React.Suspense>} />
<Route path="/study-page-4" element={<React.Suspense fallback={<>...</>}><TestPage redirectLoc={latinSquareOrder[4]}/></React.Suspense>} />
<Route path="/in-between-5" element={<React.Suspense fallback={<>...</>}><TestInfoPage2 redirectLoc="study-page-5"/></React.Suspense>} />
<Route path="/study-page-5" element={<React.Suspense fallback={<>...</>}><TestPage redirectLoc={latinSquareOrder[5]}/></React.Suspense>} />
<Route path="/in-between-6" element={<React.Suspense fallback={<>...</>}><TestInfoPage2 redirectLoc="study-page-6"/></React.Suspense>} />
<Route path="/study-page-6" element={<React.Suspense fallback={<>...</>}><TestPage2 redirectLoc={latinSquareOrder[6]}/></React.Suspense>} />
<Route path="/end" element={<React.Suspense fallback={<>...</>}><TestEndPage /></React.Suspense>} />
<Route path="*" element={<React.Suspense fallback={<>...</>}><NoPageFound /></React.Suspense>} />
<Route
path="/"
element={
<React.Suspense fallback={<>...</>}>
<TestInfoPage redirectLoc={latinSquareOrder[0]} />
</React.Suspense>
}
/>
<Route
path="/in-between-1"
element={
<React.Suspense fallback={<>...</>}>
<TestInfoPage2 redirectLoc="study-page-1" />
</React.Suspense>
}
/>
<Route
path="/study-page-1"
element={
<React.Suspense fallback={<>...</>}>
<StartPage1 redirectLoc={latinSquareOrder[1]} />
</React.Suspense>
}
/>
<Route
path="/in-between-2"
element={
<React.Suspense fallback={<>...</>}>
<TestInfoPage2 redirectLoc="study-page-2" />
</React.Suspense>
}
/>
<Route
path="/study-page-2"
element={
<React.Suspense fallback={<>...</>}>
<TestPage redirectLoc={latinSquareOrder[2]} />
</React.Suspense>
}
/>
<Route
path="/in-between-3"
element={
<React.Suspense fallback={<>...</>}>
<TestInfoPage2 redirectLoc="study-page-3" />
</React.Suspense>
}
/>
<Route
path="/study-page-3"
element={
<React.Suspense fallback={<>...</>}>
<TestPage redirectLoc={latinSquareOrder[3]} />
</React.Suspense>
}
/>
<Route
path="/in-between-4"
element={
<React.Suspense fallback={<>...</>}>
<TestInfoPage2 redirectLoc="study-page-4" />
</React.Suspense>
}
/>
<Route
path="/study-page-4"
element={
<React.Suspense fallback={<>...</>}>
<TestPage redirectLoc={latinSquareOrder[4]} />
</React.Suspense>
}
/>
<Route
path="/in-between-5"
element={
<React.Suspense fallback={<>...</>}>
<TestInfoPage2 redirectLoc="study-page-5" />
</React.Suspense>
}
/>
<Route
path="/study-page-5"
element={
<React.Suspense fallback={<>...</>}>
<TestPage redirectLoc={latinSquareOrder[5]} />
</React.Suspense>
}
/>
<Route
path="/in-between-6"
element={
<React.Suspense fallback={<>...</>}>
<TestInfoPage2 redirectLoc="study-page-6" />
</React.Suspense>
}
/>
<Route
path="/study-page-6"
element={
<React.Suspense fallback={<>...</>}>
<TestPage2 redirectLoc={latinSquareOrder[6]} />
</React.Suspense>
}
/>
<Route
path="/end"
element={
<React.Suspense fallback={<>...</>}>
<TestEndPage />
</React.Suspense>
}
/>
<Route
path="*"
element={
<React.Suspense fallback={<>...</>}>
<NoPageFound />
</React.Suspense>
}
/>
</Routes>
</React.Suspense>
<CssBaseline />

6
src/App.test.js

@ -1,7 +1,7 @@
import { render, screen } from '@testing-library/react';
import App from './App';
import { render, screen } from "@testing-library/react";
import App from "./App";
test('renders learn react link', () => {
test("renders learn react link", () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();

57
src/components/DownloadButton.jsx

@ -1,42 +1,41 @@
import React from "react";
import { getTranslation } from "../core/i18n/I18NHandler";
import { getSensorLog } from "../core/log/SensorLogger";
import { Button } from '@geist-ui/core';
import { Button } from "@geist-ui/core";
import { getUserID } from "./RandomIDComponent";
import { PARTICIPANT_NUMBER } from "../core/Constants";
function DownloadButton() {
const downloadFile = ({ data, fileName, fileType }) => {
const blob = new Blob([data], { type: fileType });
const a = document.createElement('a');
a.download = fileName;
a.href = window.URL.createObjectURL(blob);
const clickEvt = new MouseEvent('click', {
view: window,
bubbles: true,
cancelable: true,
});
a.dispatchEvent(clickEvt);
a.remove();
}
const downloadFile = ({ data, fileName, fileType }) => {
const blob = new Blob([data], { type: fileType });
const a = document.createElement("a");
a.download = fileName;
a.href = window.URL.createObjectURL(blob);
const clickEvt = new MouseEvent("click", {
view: window,
bubbles: true,
cancelable: true,
});
a.dispatchEvent(clickEvt);
a.remove();
};
const userData = {
participantId: getUserID(),
sensorLog: getSensorLog(),
participantNumber: PARTICIPANT_NUMBER,
};
const userData = {
participantId: getUserID(),
sensorLog: getSensorLog(),
participantNumber: PARTICIPANT_NUMBER
}
const exportToJson = (e) => {
e.preventDefault();
downloadFile({
data: JSON.stringify(userData),
fileName: "sensorData.json",
fileType: "text/json",
});
};
const exportToJson = e => {
e.preventDefault();
downloadFile({
data: JSON.stringify(userData),
fileName: 'sensorData.json',
fileType: 'text/json',
})
}
return (<Button onClick={exportToJson}>{getTranslation("download")}</Button>);
return <Button onClick={exportToJson}>{getTranslation("download")}</Button>;
}
export default DownloadButton;

30
src/components/InfoPageComponent.jsx

@ -1,20 +1,26 @@
import React from "react";
import { getTranslation } from "../core/i18n/I18NHandler";
import { Button } from '@geist-ui/core';
import { Button } from "@geist-ui/core";
const redirectToPage = (redirectLoc) => {
window.location.href = "./" + redirectLoc;
}
window.location.href = "./" + redirectLoc;
};
function InfoPageComponent({children, redirectLoc}) {
return (
<div>
{children}
<div>
<Button onClick={() => {redirectToPage(redirectLoc)}}>{getTranslation("continue")}</Button>
</div>
</div>
);
function InfoPageComponent({ children, redirectLoc }) {
return (
<div>
{children}
<div>
<Button
onClick={() => {
redirectToPage(redirectLoc);
}}
>
{getTranslation("continue")}
</Button>
</div>
</div>
);
}
export default InfoPageComponent;

35
src/components/RandomIDComponent.jsx

@ -1,34 +1,39 @@
import React from "react";
import { createStore } from 'zustand/vanilla'
import { persist, createJSONStorage } from 'zustand/middleware'
import { createStore } from "zustand/vanilla";
import { persist, createJSONStorage } from "zustand/middleware";
import { getTranslation } from "../core/i18n/I18NHandler";
var randomId = 0;
// State for saving participant ID globally.
export const participantIdLogState = createStore(
persist(
() => ({
participant_id: ""
}),
{
name: 'participant-id-storage', // name of the item in the storage (must be unique)
storage: createJSONStorage(() => sessionStorage), // (optional) by default, 'localStorage' is used
},
),
persist(
() => ({
participant_id: "",
}),
{
name: "participant-id-storage", // name of the item in the storage (must be unique)
storage: createJSONStorage(() => sessionStorage), // (optional) by default, 'localStorage' is used
}
)
);
const { getState, setState } = participantIdLogState;
// Can only be called once. :)
function RandomIDComponent() {
randomId = Math.floor(Math.random() * 89999) + 10000;
setState({ participant_id: "" + randomId })
return (<div><h3>Your ID:</h3><p>{randomId}</p></div>);
randomId = Math.floor(Math.random() * 89999) + 10000;
setState({ participant_id: "" + randomId });
return (
<div>
<h3>Your ID:</h3>
<p>{randomId}</p>
</div>
);
}
export function getUserID() {
return getState().participant_id;
return getState().participant_id;
}
export default RandomIDComponent;

125
src/components/questionnaire/QuestionComponent.jsx

@ -1,63 +1,80 @@
// QuestionComponent.js
import React, { useEffect } from "react";
import { Slider, Input, Radio, Checkbox, Link } from '@geist-ui/core';
import { Slider, Input, Radio, Checkbox, Link } from "@geist-ui/core";
export default function QuestionComponent(props) {
const { questionData, onUpdateAnswerData } = props;
const { questionData, onUpdateAnswerData } = props;
useEffect(() => {
// Update questionData when component mounts or props change
questionData.sData({ "id": props.id, "answerValue": questionData.answerValue });
}, [props.id, questionData.answerValue]);
useEffect(() => {
// Update questionData when component mounts or props change
questionData.sData({ id: props.id, answerValue: questionData.answerValue });
}, [props.id, questionData.answerValue]);
// Define a function to handle answer data change
const handleAnswerChange = (val) => {
questionData.sAnswerData(val);
onUpdateAnswerData(questionData.gAnswerData()); // Update answerData in parent component
};
// Define a function to handle answer data change
const handleAnswerChange = (val) => {
questionData.sAnswerData(val);
onUpdateAnswerData(questionData.gAnswerData()); // Update answerData in parent component
};
// Determine and render answer element based on props
var element;
if (props.answerType === "slider") {
element = (
<Slider
onChange={handleAnswerChange}
max={props.maxElement}
initialValue={questionData.gAnswerData()}
showMarkers
width="75%"
/>
);
} else if (props.answerType === "radio") {
const btnIndexes = Array.from({ length: props.maxElement }, (_, i) => i + 1);
element = (
<Radio.Group onChange={handleAnswerChange}>
{btnIndexes.map((x) => <Radio key={x} value={x}>{x}</Radio>)}
</Radio.Group>
);
} else if (props.answerType === "checkbox") {
const btnIndexes = Array.from({ length: props.maxElement }, (_, i) => i + 1);
element = (
<Checkbox.Group onChange={handleAnswerChange}>
{btnIndexes.map((x) => <Checkbox key={x} value={x}>{x}</Checkbox>)}
</Checkbox.Group>
);
} else {
element = (
<Input
onChange={e => handleAnswerChange(e.target.value)}
clearable
/>
);
}
// Render the component
return (
<div>
<p>{props.text}</p>
{element}
{props.referBack && <Link href={props.referBack} block></Link>}
<Link href={props.referTo} block></Link>
</div>
// Determine and render answer element based on props
var element;
if (props.answerType === "slider") {
element = (
<Slider
onChange={handleAnswerChange}
max={props.maxElement}
initialValue={questionData.gAnswerData()}
showMarkers
width="75%"
/>
);
} else if (props.answerType === "radio") {
const btnIndexes = Array.from(
{ length: props.maxElement },
(_, i) => i + 1
);
element = (
<Radio.Group onChange={handleAnswerChange}>
{btnIndexes.map((x) => (
<Radio key={x} value={x}>
{x}
</Radio>
))}
</Radio.Group>
);
} else if (props.answerType === "checkbox") {
const btnIndexes = Array.from(
{ length: props.maxElement },
(_, i) => i + 1
);
element = (
<Checkbox.Group onChange={handleAnswerChange}>
{btnIndexes.map((x) => (
<Checkbox key={x} value={x}>
{x}
</Checkbox>
))}
</Checkbox.Group>
);
} else {
element = (
<Input onChange={(e) => handleAnswerChange(e.target.value)} clearable />
);
}
// Render the component
return (
<div>
<p>{props.text}</p>
{element}
{props.referBack && (
<Link href={props.referBack} block>
</Link>
)}
<Link href={props.referTo} block>
</Link>
</div>
);
}

66
src/components/questionnaire/QuestionData.jsx

@ -1,36 +1,36 @@
import {useState} from "react";
import { useState } from "react";
export function useQuestionData() {
const [data, setData] = useState({});
function setQuestionData(questionData) {
setData(questionData);
};
function getQuestionData() {
return data;
}
function setAnswerValue(val) {
data.answerValue = val;
setData(data);
}
function getAnswerValue() {
return data.answerValue;
}
function reset() {
setData({});
};
const inputProps = {
sData: setQuestionData,
gData: getQuestionData,
sAnswerData: setAnswerValue,
gAnswerData: getAnswerValue,
r: reset
};
return inputProps;
const [data, setData] = useState({});
function setQuestionData(questionData) {
setData(questionData);
}
function getQuestionData() {
return data;
}
function setAnswerValue(val) {
data.answerValue = val;
setData(data);
}
function getAnswerValue() {
return data.answerValue;
}
function reset() {
setData({});
}
const inputProps = {
sData: setQuestionData,
gData: getQuestionData,
sAnswerData: setAnswerValue,
gAnswerData: getAnswerValue,
r: reset,
};
return inputProps;
}

44
src/components/questionnaire/QuestionnaireData.jsx

@ -2,26 +2,32 @@
import { useState, useEffect } from "react";
import { useQuestionData } from "./QuestionData";
export function useQuestionComponent(answerType, maxElement, text, referBack, referTo) {
const [answerData, setAnswerData] = useState(null);
const questionData = useQuestionData({});
export function useQuestionComponent(
answerType,
maxElement,
text,
referBack,
referTo
) {
const [answerData, setAnswerData] = useState(null);
const questionData = useQuestionData({});
useEffect(() => {
setAnswerData(questionData.gAnswerData());
}, [questionData]);
useEffect(() => {
setAnswerData(questionData.gAnswerData());
}, [questionData]);
const handleUpdateAnswerData = (data) => {
setAnswerData(data);
};
const handleUpdateAnswerData = (data) => {
setAnswerData(data);
};
return {
questionData,
answerData,
handleUpdateAnswerData, // Pass the function here
answerType,
maxElement,
text,
referBack,
referTo
};
return {
questionData,
answerData,
handleUpdateAnswerData, // Pass the function here
answerType,
maxElement,
text,
referBack,
referTo,
};
}

99
src/components/webpage_container/StudySite.jsx

@ -1,69 +1,68 @@
import React, {useEffect} from "react";
import React, { useEffect } from "react";
import h337 from "heatmap.js";
import { MOUSE_MODE, HEATMAP_MAX } from "../../core/Constants";
var stopInput = false;
var heatmapInstance = h337.create({
container: document.body,
radius: 20
container: document.body,
radius: 20,
});
const visualiseMouseData = ((mouseData) => {
mouseData.max = mouseData.data.length + 1;
heatmapInstance.setData(mouseData);
document.getElementsByTagName("canvas")[0].style.opacity = 1;
stopInput = true;
});
const visualiseMouseData = (mouseData) => {
mouseData.max = mouseData.data.length + 1;
heatmapInstance.setData(mouseData);
document.getElementsByTagName("canvas")[0].style.opacity = 1;
stopInput = true;
};
export function StudySite(props) {
useEffect(() => {
window.visualiseMouseData = visualiseMouseData;
heatmapInstance.setDataMax(HEATMAP_MAX);
var addData;
useEffect(() => {
window.visualiseMouseData = visualiseMouseData;
heatmapInstance.setDataMax(HEATMAP_MAX);
var addData;
if(MOUSE_MODE) {
const mouseDataFunc = function(ev) {
if(!stopInput) {
heatmapInstance.addData({
x: ev.x,
y: ev.y,
value: 1,
});
}
};
if (MOUSE_MODE) {
const mouseDataFunc = function (ev) {
if (!stopInput) {
heatmapInstance.addData({
x: ev.x,
y: ev.y,
value: 1,
});
}
};
addData = mouseDataFunc;
} else {
const touchDataFunc = function(ev) {
if(!stopInput) {
heatmapInstance.addData({
x: ev.layerX,
y: ev.layerY,
value: 1
});
}
};
addData = touchDataFunc;
}
addData = mouseDataFunc;
} else {
const touchDataFunc = function (ev) {
if (!stopInput) {
heatmapInstance.addData({
x: ev.layerX,
y: ev.layerY,
value: 1,
});
}
};
addData = touchDataFunc;
}
if(MOUSE_MODE) {
document.body.addEventListener("mousemove", addData, false);
} else {
document.body.addEventListener("touchmove", addData, false);
document.body.addEventListener("touchstart", addData, false);
document.body.addEventListener("touchend", addData, false);
}
if (MOUSE_MODE) {
document.body.addEventListener("mousemove", addData, false);
} else {
document.body.addEventListener("touchmove", addData, false);
document.body.addEventListener("touchstart", addData, false);
document.body.addEventListener("touchend", addData, false);
}
return () => {
delete window.visualiseMouseData;
};
});
return () => {
delete window.visualiseMouseData;
};
});
return (<>{props.children}</>);
return <>{props.children}</>;
}
export function getHeatmapData() {
return heatmapInstance.getData();
return heatmapInstance.getData();
}

29
src/components/webpage_container/WebpageBanner.jsx

@ -1,23 +1,24 @@
import { Component } from "react";
import { getTranslation } from "../../core/i18n/I18NHandler";
import { Note } from '@geist-ui/core';
import { Note } from "@geist-ui/core";
var bannerContent = "";
export default class WebpageBanner extends Component {
constructor(props) {
super(props);
bannerContent = getTranslation(props.translationKey);
}
constructor(props) {
super(props);
bannerContent = getTranslation(props.translationKey);
}
setBannerContent(content) {
bannerContent = content;
}
setBannerContent(content) {
bannerContent = content;
}
render() {
return (
<Note type="warning" label={false}>{bannerContent}</Note>
);
}
render() {
return (
<Note type="warning" label={false}>
{bannerContent}
</Note>
);
}
}

63
src/core/audio/AudioHandler.jsx

@ -1,38 +1,33 @@
import {Component} from 'react';
import { Component } from "react";
import * as Tone from "tone";
import { pushToSonificationLog } from '../log/SensorLogger';
import { pushToSonificationLog } from "../log/SensorLogger";
export default class AudioHandler extends Component {
playTabEarconSonification() {
pushToSonificationLog("tab_earcon");
}
playTabModelSonification() {
pushToSonificationLog("tab_model");
}
playDetailEarconSonification() {
pushToSonificationLog("detail_earcon");
}
playDetailModelSonification() {
pushToSonificationLog("detail_model");
}
setRotation(angle) {
Tone.Listener.forwardX.value = Math.sin(angle);
Tone.Listener.forwardY.value = 0;
Tone.Listener.forwardZ.value = -Math.cos(angle);
}
setRotationRamp(panner, angle, rampTime) {
panner.positionX.rampTo(Math.sin(angle), rampTime);
panner.positionY.rampTo(0, rampTime);
panner.positionZ.rampTo(-Math.cos(angle), rampTime);
}
playTabEarconSonification() {
pushToSonificationLog("tab_earcon");
}
playTabModelSonification() {
pushToSonificationLog("tab_model");
}
playDetailEarconSonification() {
pushToSonificationLog("detail_earcon");
}
playDetailModelSonification() {
pushToSonificationLog("detail_model");
}
setRotation(angle) {
Tone.Listener.forwardX.value = Math.sin(angle);
Tone.Listener.forwardY.value = 0;
Tone.Listener.forwardZ.value = -Math.cos(angle);
}
setRotationRamp(panner, angle, rampTime) {
panner.positionX.rampTo(Math.sin(angle), rampTime);
panner.positionY.rampTo(0, rampTime);
panner.positionZ.rampTo(-Math.cos(angle), rampTime);
}
}

30
src/core/audio/ChordHelper.jsx

@ -1,26 +1,28 @@
import * as Tone from "tone";
export function buildChord(notes, octave) {
return notes.map(note => {return (note + octave);})
return notes.map((note) => {
return note + octave;
});
}
export function buildNote(note, octave) {
return (note + octave);
return note + octave;
}
export function generateChord(rootNote, chordType) {
// Define intervals for different chord types
const chordIntervals = {
'major': [0, 4, 7],
'minor': [0, 3, 7],
'7th': [0, 4, 7, 10],
'diminished': [0, 3, 6],
'augmented': [0, 4, 8],
'sus2': [0, 2, 7],
'sus4': [0, 5, 7]
};
// Define intervals for different chord types
const chordIntervals = {
major: [0, 4, 7],
minor: [0, 3, 7],
"7th": [0, 4, 7, 10],
diminished: [0, 3, 6],
augmented: [0, 4, 8],
sus2: [0, 2, 7],
sus4: [0, 5, 7],
};
const intervals = chordIntervals[chordType];
const intervals = chordIntervals[chordType];
return Tone.Frequency(rootNote).harmonize(intervals);
return Tone.Frequency(rootNote).harmonize(intervals);
}

2
src/core/i18n/I18NHandler.jsx

@ -1,5 +1,5 @@
import i18n from "./i18n";
export function getTranslation(translationKey) {
return i18n.t(translationKey);
return i18n.t(translationKey);
}

10
src/core/i18n/i18n.js

@ -1,7 +1,7 @@
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import Backend from "i18next-http-backend";
import LanguageDetector from 'i18next-browser-languagedetector';
import LanguageDetector from "i18next-browser-languagedetector";
i18n
.use(Backend)
@ -10,10 +10,10 @@ i18n
.init({
fallbackLng: "en",
interpolation: {
escapeValue: false // react already safes from xss
}
escapeValue: false, // react already safes from xss
},
});
i18n.changeLanguage('en'); // For now to control the language... Later on, not necessary...
i18n.changeLanguage("en"); // For now to control the language... Later on, not necessary...
export default i18n;
export default i18n;

6
src/core/log/RouteTracker.jsx

@ -1,6 +1,6 @@
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
import { pushToVisitLog } from './SensorLogger';
import { useEffect } from "react";
import { useLocation } from "react-router-dom";
import { pushToVisitLog } from "./SensorLogger";
const RouteTracker = () => {
const location = useLocation();

102
src/core/log/SensorLogger.jsx

@ -1,47 +1,57 @@
import { getState, setState } from '../../App';
export function pushToMouseLog(logElement) {
const sensorLog = getState().sensorLog;
sensorLog.mouseLog.push(logElement);
setState({ sensorLog: sensorLog });
}
export function pushToClickLog(logElement) {
// Filter out dummy body objects
if(logElement.target.localName === "body") {
return;
}
const sensorLog = getState().sensorLog;
sensorLog.clickedElements.push(logElement);
setState({ sensorLog: sensorLog });
}
export function pushToVisitLog(logElement) {
const sensorLog = getState().sensorLog;
// Filter out double visit pushes...
const visitedSitesAmount = sensorLog.visitedSites.length;
if(visitedSitesAmount > 0) {
const lastVisitedElement = sensorLog.visitedSites[visitedSitesAmount - 1];
if(lastVisitedElement.path === logElement.path && logElement.timestamp - lastVisitedElement.timestamp < 500) {
return;
}
sensorLog.visitedSites.push(logElement);
setState({ sensorLog: sensorLog });
}
}
export function pushToSonificationLog(logElement) {
const sensorLog = getState().sensorLog;
sensorLog.playedSonifications.push(logElement);
setState({ sensorLog: sensorLog });
}
export function getSensorLog() {
return getState().sensorLog;
}
export function resetSensorLog() {
setState({ sensorLog: {mouseLog: [], clickedElements: [], visitedSites: [], playedSonifications: []}});
import { getState, setState } from "../../App";
export function pushToMouseLog(logElement) {
const sensorLog = getState().sensorLog;
sensorLog.mouseLog.push(logElement);
setState({ sensorLog: sensorLog });
}
export function pushToClickLog(logElement) {
// Filter out dummy body objects
if (logElement.target.localName === "body") {
return;
}
const sensorLog = getState().sensorLog;
sensorLog.clickedElements.push(logElement);
setState({ sensorLog: sensorLog });
}
export function pushToVisitLog(logElement) {
const sensorLog = getState().sensorLog;
// Filter out double visit pushes...
const visitedSitesAmount = sensorLog.visitedSites.length;
if (visitedSitesAmount > 0) {
const lastVisitedElement = sensorLog.visitedSites[visitedSitesAmount - 1];
if (
lastVisitedElement.path === logElement.path &&
logElement.timestamp - lastVisitedElement.timestamp < 500
) {
return;
}
sensorLog.visitedSites.push(logElement);
setState({ sensorLog: sensorLog });
}
}
export function pushToSonificationLog(logElement) {
const sensorLog = getState().sensorLog;
sensorLog.playedSonifications.push(logElement);
setState({ sensorLog: sensorLog });
}
export function getSensorLog() {
return getState().sensorLog;
}
export function resetSensorLog() {
setState({
sensorLog: {
mouseLog: [],
clickedElements: [],
visitedSites: [],
playedSonifications: [],
},
});
}

23
src/index.js

@ -1,23 +1,22 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { BrowserRouter } from "react-router-dom";
import "./core/i18n/i18n";
import { pushToClickLog } from './core/log/SensorLogger';
import { pushToClickLog } from "./core/log/SensorLogger";
document.onclick = function(event) {
document.onclick = function (event) {
pushToClickLog(event);
};
const root = ReactDOM.createRoot(document.getElementById('root'));
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);

16
src/interfaces/DataComponent.jsx

@ -1,14 +1,14 @@
import ResetableComponent from "./ResetableComponent.jsx";
export default class DataComponent extends ResetableComponent {
constructor(componentId) {
super();
this.compId = componentId;
}
constructor(componentId) {
super();
this.compId = componentId;
}
exportData() {}
getId() { return this.compId; }
exportData() {}
getId() {
return this.compId;
}
}

6
src/interfaces/ResetableComponent.jsx

@ -1,7 +1,5 @@
import React from 'react';
import React from "react";
export default class ResetableComponent extends React.Component {
reset() {}
reset() {}
}

7
src/pages/NoPageFound.jsx

@ -1,8 +1,7 @@
import React from "react";
export default class NoPageFound extends React.Component {
render() {
return (<p>No Page Found</p>);
}
render() {
return <p>No Page Found</p>;
}
}

10
src/pages/TestEndPage.jsx

@ -2,10 +2,12 @@ import React from "react";
import DownloadButton from "../components/DownloadButton";
function TestInfoPage() {
return (<div>
<p>Thank you for participating! </p>
<DownloadButton />
</div>);
return (
<div>
<p>Thank you for participating! </p>
<DownloadButton />
</div>
);
}
export default TestInfoPage;

22
src/pages/TestInfoPage.jsx

@ -2,11 +2,23 @@ import React from "react";
import InfoPageComponent from "../components/InfoPageComponent";
import RandomIDComponent from "../components/RandomIDComponent";
function TestInfoPage({redirectLoc}) {
return (<InfoPageComponent redirectLoc={redirectLoc}>
<p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.</p>
<RandomIDComponent />
</InfoPageComponent>);
function TestInfoPage({ redirectLoc }) {
return (
<InfoPageComponent redirectLoc={redirectLoc}>
<p>
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy
eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam
voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet
clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit
amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam
nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,
sed diam voluptua. At vero eos et accusam et justo duo dolores et ea
rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem
ipsum dolor sit amet.
</p>
<RandomIDComponent />
</InfoPageComponent>
);
}
export default TestInfoPage;

20
src/pages/TestInfoPage2.jsx

@ -1,10 +1,22 @@
import React from "react";
import InfoPageComponent from "../components/InfoPageComponent";
function TestInfoPage2({redirectLoc}) {
return (<InfoPageComponent redirectLoc={redirectLoc}>
<p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.</p>
</InfoPageComponent>);
function TestInfoPage2({ redirectLoc }) {
return (
<InfoPageComponent redirectLoc={redirectLoc}>
<p>
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy
eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam
voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet
clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit
amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam
nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,
sed diam voluptua. At vero eos et accusam et justo duo dolores et ea
rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem
ipsum dolor sit amet.
</p>
</InfoPageComponent>
);
}
export default TestInfoPage2;

32
src/pages/TestPage.jsx

@ -1,17 +1,31 @@
import React from "react";
import { getTranslation } from "../core/i18n/I18NHandler";
import WebpageBanner from "../components/webpage_container/WebpageBanner";
import { StudySite, getHeatmapData } from "../components/webpage_container/StudySite";
import {
StudySite,
getHeatmapData,
} from "../components/webpage_container/StudySite";
import { pushToMouseLog, getSensorLog } from "../core/log/SensorLogger";
import { Button } from '@geist-ui/core';
import { Button } from "@geist-ui/core";
function TestPage({redirectLoc}) {
var clickFunction = function() {
pushToMouseLog(getHeatmapData());
window.location.href = "./" + redirectLoc;
};
return (<StudySite><WebpageBanner translationKey="hello_world" /><p onClick={() => {clickFunction()}}>{getTranslation("hello_world")}</p><Button onClick={clickFunction}>Ye</Button></StudySite>);
function TestPage({ redirectLoc }) {
var clickFunction = function () {
pushToMouseLog(getHeatmapData());
window.location.href = "./" + redirectLoc;
};
return (
<StudySite>
<WebpageBanner translationKey="hello_world" />
<p
onClick={() => {
clickFunction();
}}
>
{getTranslation("hello_world")}
</p>
<Button onClick={clickFunction}>Ye</Button>
</StudySite>
);
}
export default TestPage;

31
src/pages/TestPage2.jsx

@ -1,16 +1,31 @@
import React from "react";
import { getTranslation } from "../core/i18n/I18NHandler";
import WebpageBanner from "../components/webpage_container/WebpageBanner";
import { StudySite, getHeatmapData } from "../components/webpage_container/StudySite";
import {
StudySite,
getHeatmapData,
} from "../components/webpage_container/StudySite";
import { pushToMouseLog } from "../core/log/SensorLogger";
import { Button } from '@geist-ui/core';
import { Button } from "@geist-ui/core";
function TestPage2({redirectLoc}) {
var clickFunction = function() {
pushToMouseLog(getHeatmapData());
window.location.href = "./" + redirectLoc;
};
return (<StudySite><WebpageBanner translationKey="hello_world" /><p>{getTranslation("hello_world")}</p><Button onClick={() => {clickFunction()}}>Go on...</Button></StudySite>);
function TestPage2({ redirectLoc }) {
var clickFunction = function () {
pushToMouseLog(getHeatmapData());
window.location.href = "./" + redirectLoc;
};
return (
<StudySite>
<WebpageBanner translationKey="hello_world" />
<p>{getTranslation("hello_world")}</p>
<Button
onClick={() => {
clickFunction();
}}
>
Go on...
</Button>
</StudySite>
);
}
export default TestPage2;

40
src/pages/TestQuestionnaire.jsx

@ -1,22 +1,36 @@
import React from "react";
import { useParams } from 'react-router-dom';
import { useParams } from "react-router-dom";
import QuestionComponent from "../components/questionnaire/QuestionComponent";
import { useQuestionComponent } from "../components/questionnaire/QuestionnaireData";
export default function TestQuestionnaire() {
const { id } = useParams()
const { id } = useParams();
const q1Props = useQuestionComponent("checkbox", 4, "Lorem Ipsum", "/info", "./q2");
const q2Props = useQuestionComponent("slider", 8, "Lorem Ipsum Ye", "./q1", "/info");
const q1Props = useQuestionComponent(
"checkbox",
4,
"Lorem Ipsum",
"/info",
"./q2"
);
const q2Props = useQuestionComponent(
"slider",
8,
"Lorem Ipsum Ye",
"./q1",
"/info"
);
const questionProperties = {"q1": q1Props, "q2": q2Props};
const questionProperty = questionProperties[id];
const questionProperties = { q1: q1Props, q2: q2Props };
const questionProperty = questionProperties[id];
return (
<div>
<QuestionComponent {...questionProperty} onUpdateAnswerData={questionProperty.handleUpdateAnswerData} />
<p>Current Answer Data: {JSON.stringify(questionProperty.answerData)}</p>
</div>
);
return (
<div>
<QuestionComponent
{...questionProperty}
onUpdateAnswerData={questionProperty.handleUpdateAnswerData}
/>
<p>Current Answer Data: {JSON.stringify(questionProperty.answerData)}</p>
</div>
);
}

212
src/pages/study_site_1/StartPage1.jsx

@ -1,81 +1,155 @@
import React from "react";
import { getTranslation } from "../../core/i18n/I18NHandler";
import WebpageBanner from "../../components/webpage_container/WebpageBanner";
import { StudySite, getHeatmapData } from "../../components/webpage_container/StudySite";
import {
StudySite,
getHeatmapData,
} from "../../components/webpage_container/StudySite";
import { pushToMouseLog, getSensorLog } from "../../core/log/SensorLogger";
import { Collapse, Text, Spacer } from "@geist-ui/core";
//import {logo} from "/public/images/budget_bird_logo.png";
function StartPage1({redirectLoc}) {
var saveMouseLog = function() {
pushToMouseLog(getHeatmapData());
//window.location.href = "./" + redirectLoc;
};
function StartPage1({ redirectLoc }) {
var saveMouseLog = function () {
pushToMouseLog(getHeatmapData());
//window.location.href = "./" + redirectLoc;
};
// TODO THINK IF I WANT TO JUST USE AN OVERLAY FOR OTHER DUMMY LINKS, WHICH ARENT CORRECT???
// IS THAT OKAY???
// TODO THINK IF I WANT TO JUST USE AN OVERLAY FOR OTHER DUMMY LINKS, WHICH ARENT CORRECT???
// IS THAT OKAY???
return (<StudySite>
<WebpageBanner translationKey="task_1_info" />
<Spacer h={2}/>
<img src="/images/budget_bird_logo.png" width="150px" alt="BudgetBird Airlines Logo" />
<Spacer h={1}/>
<h3>Frequently Asked Questions</h3>
<Spacer h={2}/>
<Collapse.Group>
<Collapse title="Under what conditions can I rebook?">
<Text>You can change your flight bookings providing the fare conditions of your ticket permit this.
You can find out whether your ticket can be rebooked and whether there is a fee for this on your booking confirmation, or contact your travel agency or the Service Centre.
Alternatively, you can also rebook your flight details under <a href="./">My Bookings.</a></Text>
</Collapse>
<Collapse title="Can I make a name change after I have booked?">
<Text>It is not possible to make a retroactive name change for a booking. Lufthansa shall provide the transport service to the passenger named in the ticket only.
The BudgetBird Service Team will be happy to help you if you require further information.</Text>
</Collapse>
<Collapse title="What are the upper weight limits for free baggage?">
<Text>The maximum weight limit for your flight can be checked </Text>
<Text>
The maximum size per piece of baggage is 158 cm (width X + height Y + depth Z).
</Text>
<Text>Larger, heavier or additional bags are carried as excess baggage. You can find the prices in our baggage calculator.</Text>
</Collapse>
<Collapse title="What are the passenger rights you have in case of rescheduling or cancellation?">
<Text>Since BudgetBird Airlines offers no flights outside the European Union the <a href="https://europa.eu/youreurope/citizens/travel/passenger-rights/air/index_en.htm">European Air Passenger Rights</a> will apply.</Text>
</Collapse>
<Collapse title="What costs will incur for rebooking my ticket?">
<Text>You can change your flight bookings provided that the fare conditions of your ticket permit this.
You can find out whether your ticket can be rebooked and whether there is a fee for this on your booking confirmation, or contact your travel agency or the Service Centre.
Alternatively, you can also make a change to your flight details in the <a href="./">My Bookings</a> section. Depending on the alternative flight you choose, there may be surcharges on a higher fare. The amount of the surcharge depends on the availability of the selected booking class and will be communicated to you during the rebooking process.</Text>
</Collapse>
<Collapse title="How do I get my money returned to me after a refund?">
<Text>In principle, the refund is paid using the same method that you originally used to pay for the ticket, in other words, if you paid for your ticket by credit card, the refund will be credited to the same credit card account.</Text>
</Collapse>
<Collapse title="How do I make a reservation?">
<Text>To make a reservation, please contact us in the following way: </Text>
<Text>Contact Details: </Text>
<Text><a href="tel:+31 99 999 9999">+31 99 999 9999</a>
<Text>Monday - Sunday open for 24 hours.</Text>
<Text>(Standard phone charges apply.)</Text></Text>
<Text><a href="mailto:reservation.budgetbird@example.com">reservation.budgetbird@example.com</a></Text>
</Collapse>
<Collapse title="My flight was cancelled. How do I get my refund?">
<Text>When your ticket has not been used, the ticket price including all taxes and fees will be refunded to the original method of payment.
If you have already used one or more segments of the journey, the unused portion of the fare value, taxes and fees will be calculated and the ticket refunded accordingly on a pro rata basis.</Text>
</Collapse>
<Collapse title="How can I contact the Customer Support?">
<Text>Depending on your concern, you can contact Customer Support in the following ways:</Text>
<ul>
<li><a href="./">Flight Problems</a></li>
<li><a href="./">Rebooking</a></li>
<li><a href={redirectLoc} onclick={saveMouseLog()}>Delay/Cancellation Refunds</a></li>
<li><a href="./">General Concerns</a></li>
</ul>
</Collapse>
<Collapse title="How does the BudgetBird Miles program work?">
<Text>For every 100 kilometers travelled on any flight with BudgetBird Airlines, you will gain one BudgetBird Mile. These BudgetBird Miles can be used within the BudgetBird Miles Store, in exchange for goodies, discounts or free BudgetBird flight tickets.</Text>
</Collapse>
</Collapse.Group>
</StudySite>);
return (
<StudySite>
<WebpageBanner translationKey="task_1_info" />
<Spacer h={2} />
<img
src="/images/budget_bird_logo.png"
width="150px"
alt="BudgetBird Airlines Logo"
/>
<Spacer h={1} />
<h3>Frequently Asked Questions</h3>
<Spacer h={2} />
<Collapse.Group>
<Collapse title="Under what conditions can I rebook?">
<Text>
You can change your flight bookings providing the fare conditions of
your ticket permit this. You can find out whether your ticket can be
rebooked and whether there is a fee for this on your booking
confirmation, or contact your travel agency or the Service Centre.
Alternatively, you can also rebook your flight details under{" "}
<a href="./">My Bookings.</a>
</Text>
</Collapse>
<Collapse title="Can I make a name change after I have booked?">
<Text>
It is not possible to make a retroactive name change for a booking.
Lufthansa shall provide the transport service to the passenger named
in the ticket only. The BudgetBird Service Team will be happy to
help you if you require further information.
</Text>
</Collapse>
<Collapse title="What are the upper weight limits for free baggage?">
<Text>The maximum weight limit for your flight can be checked </Text>
<Text>
The maximum size per piece of baggage is 158 cm (width X + height Y
+ depth Z).
</Text>
<Text>
Larger, heavier or additional bags are carried as excess baggage.
You can find the prices in our baggage calculator.
</Text>
</Collapse>
<Collapse title="What are the passenger rights you have in case of rescheduling or cancellation?">
<Text>
Since BudgetBird Airlines offers no flights outside the European
Union the{" "}
<a href="https://europa.eu/youreurope/citizens/travel/passenger-rights/air/index_en.htm">
European Air Passenger Rights
</a>{" "}
will apply.
</Text>
</Collapse>
<Collapse title="What costs will incur for rebooking my ticket?">
<Text>
You can change your flight bookings provided that the fare
conditions of your ticket permit this. You can find out whether your
ticket can be rebooked and whether there is a fee for this on your
booking confirmation, or contact your travel agency or the Service
Centre. Alternatively, you can also make a change to your flight
details in the <a href="./">My Bookings</a> section. Depending on
the alternative flight you choose, there may be surcharges on a
higher fare. The amount of the surcharge depends on the availability
of the selected booking class and will be communicated to you during
the rebooking process.
</Text>
</Collapse>
<Collapse title="How do I get my money returned to me after a refund?">
<Text>
In principle, the refund is paid using the same method that you
originally used to pay for the ticket, in other words, if you paid
for your ticket by credit card, the refund will be credited to the
same credit card account.
</Text>
</Collapse>
<Collapse title="How do I make a reservation?">
<Text>
To make a reservation, please contact us in the following way:{" "}
</Text>
<Text>Contact Details: </Text>
<Text>
<a href="tel:+31 99 999 9999">+31 99 999 9999</a>
<Text>Monday - Sunday open for 24 hours.</Text>
<Text>(Standard phone charges apply.)</Text>
</Text>
<Text>
<a href="mailto:reservation.budgetbird@example.com">
reservation.budgetbird@example.com
</a>
</Text>
</Collapse>
<Collapse title="My flight was cancelled. How do I get my refund?">
<Text>
When your ticket has not been used, the ticket price including all
taxes and fees will be refunded to the original method of payment.
If you have already used one or more segments of the journey, the
unused portion of the fare value, taxes and fees will be calculated
and the ticket refunded accordingly on a pro rata basis.
</Text>
</Collapse>
<Collapse title="How can I contact the Customer Support?">
<Text>
Depending on your concern, you can contact Customer Support in the
following ways:
</Text>
<ul>
<li>
<a href="./">Flight Problems</a>
</li>
<li>
<a href="./">Rebooking</a>
</li>
<li>
<a href={redirectLoc} onclick={saveMouseLog()}>
Delay/Cancellation Refunds
</a>
</li>
<li>
<a href="./">General Concerns</a>
</li>
</ul>
</Collapse>
<Collapse title="How does the BudgetBird Miles program work?">
<Text>
For every 100 kilometers travelled on any flight with BudgetBird
Airlines, you will gain one BudgetBird Mile. These BudgetBird Miles
can be used within the BudgetBird Miles Store, in exchange for
goodies, discounts or free BudgetBird flight tickets.
</Text>
</Collapse>
</Collapse.Group>
</StudySite>
);
}
export default StartPage1;
Loading…
Cancel
Save