Browse Source

fix: Reformat whole project.

master
Denis Thiessen 6 months ago
parent
commit
9b5cae0d53
  1. 181
      src/App.js
  2. 6
      src/App.test.js
  3. 25
      src/components/DownloadButton.jsx
  4. 12
      src/components/InfoPageComponent.jsx
  5. 21
      src/components/RandomIDComponent.jsx
  6. 41
      src/components/questionnaire/QuestionComponent.jsx
  7. 6
      src/components/questionnaire/QuestionData.jsx
  8. 10
      src/components/questionnaire/QuestionnaireData.jsx
  9. 11
      src/components/webpage_container/StudySite.jsx
  10. 7
      src/components/webpage_container/WebpageBanner.jsx
  11. 9
      src/core/audio/AudioHandler.jsx
  12. 20
      src/core/audio/ChordHelper.jsx
  13. 8
      src/core/i18n/i18n.js
  14. 6
      src/core/log/RouteTracker.jsx
  15. 16
      src/core/log/SensorLogger.jsx
  16. 15
      src/index.js
  17. 6
      src/interfaces/DataComponent.jsx
  18. 4
      src/interfaces/ResetableComponent.jsx
  19. 3
      src/pages/NoPageFound.jsx
  20. 6
      src/pages/TestEndPage.jsx
  21. 18
      src/pages/TestInfoPage.jsx
  22. 18
      src/pages/TestInfoPage2.jsx
  23. 22
      src/pages/TestPage.jsx
  24. 21
      src/pages/TestPage2.jsx
  25. 28
      src/pages/TestQuestionnaire.jsx
  26. 130
      src/pages/study_site_1/StartPage1.jsx

181
src/App.js

@ -1,9 +1,9 @@
import React from "react"; import React from "react";
import { Route, Routes } from "react-router-dom"; 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 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 { PARTICIPANT_NUMBER } from "./core/Constants";
import { StudySite } from "./components/webpage_container/StudySite"; 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 TestEndPage = React.lazy(() => import("./pages/TestEndPage"));
const StartPage1 = React.lazy(() => import("./pages/study_site_1/StartPage1")); const StartPage1 = React.lazy(() => import("./pages/study_site_1/StartPage1"));
export const sensorLogState = createStore( export const sensorLogState = createStore(
persist( 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 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 = [ const latinSquare = [
[1, 2, 3, 4, 5, 6], [1, 2, 3, 4, 5, 6],
@ -36,11 +41,11 @@ const latinSquare = [
[3, 4, 5, 1, 6, 2], [3, 4, 5, 1, 6, 2],
[4, 6, 1, 2, 3, 5], [4, 6, 1, 2, 3, 5],
[2, 3, 6, 5, 4, 1], [2, 3, 6, 5, 4, 1],
[5, 1, 4, 6, 2, 3]
[5, 1, 4, 6, 2, 3],
]; ];
const getLatinSquareIndex = function (participantNumber) { const getLatinSquareIndex = function (participantNumber) {
return ((participantNumber - 1) % latinSquare.length);
return (participantNumber - 1) % latinSquare.length;
}; };
const findIndexOfOrderElement = function (el, order) { const findIndexOfOrderElement = function (el, order) {
@ -56,26 +61,31 @@ const getLatinSquareOrder = function(participantNumber) {
const latinSquareElement = latinSquare[latinSquareIndex]; const latinSquareElement = latinSquare[latinSquareIndex];
const originalOrder = [1, 2, 3, 4, 5, 6]; const originalOrder = [1, 2, 3, 4, 5, 6];
var latinSquareRedirectOrder = []; var latinSquareRedirectOrder = [];
latinSquareRedirectOrder.push(("in-between-" + latinSquareElement[0]));
latinSquareRedirectOrder.push("in-between-" + latinSquareElement[0]);
for (var originalOrderElement of originalOrder) { for (var originalOrderElement of originalOrder) {
const indexElement = findIndexOfOrderElement(originalOrderElement, latinSquareElement);
const indexElement = findIndexOfOrderElement(
originalOrderElement,
latinSquareElement
);
if (indexElement === originalOrder.length - 1) { if (indexElement === originalOrder.length - 1) {
latinSquareRedirectOrder.push("end"); latinSquareRedirectOrder.push("end");
continue; 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]; const nextRedirctElement = latinSquareElement[nextOrderElementIndex];
latinSquareRedirectOrder.push(("in-between-" + nextRedirctElement ));
latinSquareRedirectOrder.push("in-between-" + nextRedirctElement);
} }
return latinSquareRedirectOrder; return latinSquareRedirectOrder;
}; };
function App() { function App() {
// TODO FIX... // TODO FIX...
/* /*
There is an weird interaction/error with the lazy loading of the routers There is an weird interaction/error with the lazy loading of the routers
@ -88,7 +98,7 @@ function App() {
while (now - start < ms) { while (now - start < ms) {
now = Date.now(); now = Date.now();
} }
}
};
wait(200); wait(200);
@ -99,21 +109,126 @@ function App() {
<React.Suspense fallback="loading"> <React.Suspense fallback="loading">
<RouteTracker /> <RouteTracker />
<Routes> <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> </Routes>
</React.Suspense> </React.Suspense>
<CssBaseline /> <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 />); render(<App />);
const linkElement = screen.getByText(/learn react/i); const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument(); expect(linkElement).toBeInTheDocument();

25
src/components/DownloadButton.jsx

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

12
src/components/InfoPageComponent.jsx

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

21
src/components/RandomIDComponent.jsx

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

41
src/components/questionnaire/QuestionComponent.jsx

@ -1,13 +1,13 @@
// QuestionComponent.js // QuestionComponent.js
import React, { useEffect } from "react"; 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) { export default function QuestionComponent(props) {
const { questionData, onUpdateAnswerData } = props; const { questionData, onUpdateAnswerData } = props;
useEffect(() => { useEffect(() => {
// Update questionData when component mounts or props change // Update questionData when component mounts or props change
questionData.sData({ "id": props.id, "answerValue": questionData.answerValue });
questionData.sData({ id: props.id, answerValue: questionData.answerValue });
}, [props.id, questionData.answerValue]); }, [props.id, questionData.answerValue]);
// Define a function to handle answer data change // Define a function to handle answer data change
@ -29,25 +29,36 @@ export default function QuestionComponent(props) {
/> />
); );
} else if (props.answerType === "radio") { } else if (props.answerType === "radio") {
const btnIndexes = Array.from({ length: props.maxElement }, (_, i) => i + 1);
const btnIndexes = Array.from(
{ length: props.maxElement },
(_, i) => i + 1
);
element = ( element = (
<Radio.Group onChange={handleAnswerChange}> <Radio.Group onChange={handleAnswerChange}>
{btnIndexes.map((x) => <Radio key={x} value={x}>{x}</Radio>)}
{btnIndexes.map((x) => (
<Radio key={x} value={x}>
{x}
</Radio>
))}
</Radio.Group> </Radio.Group>
); );
} else if (props.answerType === "checkbox") { } else if (props.answerType === "checkbox") {
const btnIndexes = Array.from({ length: props.maxElement }, (_, i) => i + 1);
const btnIndexes = Array.from(
{ length: props.maxElement },
(_, i) => i + 1
);
element = ( element = (
<Checkbox.Group onChange={handleAnswerChange}> <Checkbox.Group onChange={handleAnswerChange}>
{btnIndexes.map((x) => <Checkbox key={x} value={x}>{x}</Checkbox>)}
{btnIndexes.map((x) => (
<Checkbox key={x} value={x}>
{x}
</Checkbox>
))}
</Checkbox.Group> </Checkbox.Group>
); );
} else { } else {
element = ( element = (
<Input
onChange={e => handleAnswerChange(e.target.value)}
clearable
/>
<Input onChange={(e) => handleAnswerChange(e.target.value)} clearable />
); );
} }
@ -56,8 +67,14 @@ export default function QuestionComponent(props) {
<div> <div>
<p>{props.text}</p> <p>{props.text}</p>
{element} {element}
{props.referBack && <Link href={props.referBack} block></Link>}
<Link href={props.referTo} block></Link>
{props.referBack && (
<Link href={props.referBack} block>
</Link>
)}
<Link href={props.referTo} block>
</Link>
</div> </div>
); );
} }

6
src/components/questionnaire/QuestionData.jsx

@ -5,7 +5,7 @@ export function useQuestionData() {
function setQuestionData(questionData) { function setQuestionData(questionData) {
setData(questionData); setData(questionData);
};
}
function getQuestionData() { function getQuestionData() {
return data; return data;
@ -22,14 +22,14 @@ export function useQuestionData() {
function reset() { function reset() {
setData({}); setData({});
};
}
const inputProps = { const inputProps = {
sData: setQuestionData, sData: setQuestionData,
gData: getQuestionData, gData: getQuestionData,
sAnswerData: setAnswerValue, sAnswerData: setAnswerValue,
gAnswerData: getAnswerValue, gAnswerData: getAnswerValue,
r: reset
r: reset,
}; };
return inputProps; return inputProps;

10
src/components/questionnaire/QuestionnaireData.jsx

@ -2,7 +2,13 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { useQuestionData } from "./QuestionData"; import { useQuestionData } from "./QuestionData";
export function useQuestionComponent(answerType, maxElement, text, referBack, referTo) {
export function useQuestionComponent(
answerType,
maxElement,
text,
referBack,
referTo
) {
const [answerData, setAnswerData] = useState(null); const [answerData, setAnswerData] = useState(null);
const questionData = useQuestionData({}); const questionData = useQuestionData({});
@ -22,6 +28,6 @@ export function useQuestionComponent(answerType, maxElement, text, referBack, re
maxElement, maxElement,
text, text,
referBack, referBack,
referTo
referTo,
}; };
} }

11
src/components/webpage_container/StudySite.jsx

@ -6,18 +6,17 @@ var stopInput = false;
var heatmapInstance = h337.create({ var heatmapInstance = h337.create({
container: document.body, container: document.body,
radius: 20
radius: 20,
}); });
const visualiseMouseData = ((mouseData) => {
const visualiseMouseData = (mouseData) => {
mouseData.max = mouseData.data.length + 1; mouseData.max = mouseData.data.length + 1;
heatmapInstance.setData(mouseData); heatmapInstance.setData(mouseData);
document.getElementsByTagName("canvas")[0].style.opacity = 1; document.getElementsByTagName("canvas")[0].style.opacity = 1;
stopInput = true; stopInput = true;
});
};
export function StudySite(props) { export function StudySite(props) {
useEffect(() => { useEffect(() => {
window.visualiseMouseData = visualiseMouseData; window.visualiseMouseData = visualiseMouseData;
heatmapInstance.setDataMax(HEATMAP_MAX); heatmapInstance.setDataMax(HEATMAP_MAX);
@ -41,7 +40,7 @@ export function StudySite(props) {
heatmapInstance.addData({ heatmapInstance.addData({
x: ev.layerX, x: ev.layerX,
y: ev.layerY, y: ev.layerY,
value: 1
value: 1,
}); });
} }
}; };
@ -61,7 +60,7 @@ export function StudySite(props) {
}; };
}); });
return (<>{props.children}</>);
return <>{props.children}</>;
} }
export function getHeatmapData() { export function getHeatmapData() {

7
src/components/webpage_container/WebpageBanner.jsx

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

9
src/core/audio/AudioHandler.jsx

@ -1,26 +1,21 @@
import {Component} from 'react';
import { Component } from "react";
import * as Tone from "tone"; import * as Tone from "tone";
import { pushToSonificationLog } from '../log/SensorLogger';
import { pushToSonificationLog } from "../log/SensorLogger";
export default class AudioHandler extends Component { export default class AudioHandler extends Component {
playTabEarconSonification() { playTabEarconSonification() {
pushToSonificationLog("tab_earcon"); pushToSonificationLog("tab_earcon");
} }
playTabModelSonification() { playTabModelSonification() {
pushToSonificationLog("tab_model"); pushToSonificationLog("tab_model");
} }
playDetailEarconSonification() { playDetailEarconSonification() {
pushToSonificationLog("detail_earcon"); pushToSonificationLog("detail_earcon");
} }
playDetailModelSonification() { playDetailModelSonification() {
pushToSonificationLog("detail_model"); pushToSonificationLog("detail_model");
} }

20
src/core/audio/ChordHelper.jsx

@ -1,23 +1,25 @@
import * as Tone from "tone"; import * as Tone from "tone";
export function buildChord(notes, octave) { export function buildChord(notes, octave) {
return notes.map(note => {return (note + octave);})
return notes.map((note) => {
return note + octave;
});
} }
export function buildNote(note, octave) { export function buildNote(note, octave) {
return (note + octave);
return note + octave;
} }
export function generateChord(rootNote, chordType) { export function generateChord(rootNote, chordType) {
// Define intervals for different chord types // Define intervals for different chord types
const chordIntervals = { 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]
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];

8
src/core/i18n/i18n.js

@ -1,7 +1,7 @@
import i18n from "i18next"; import i18n from "i18next";
import { initReactI18next } from "react-i18next"; import { initReactI18next } from "react-i18next";
import Backend from "i18next-http-backend"; import Backend from "i18next-http-backend";
import LanguageDetector from 'i18next-browser-languagedetector';
import LanguageDetector from "i18next-browser-languagedetector";
i18n i18n
.use(Backend) .use(Backend)
@ -10,10 +10,10 @@ i18n
.init({ .init({
fallbackLng: "en", fallbackLng: "en",
interpolation: { 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 RouteTracker = () => {
const location = useLocation(); const location = useLocation();

16
src/core/log/SensorLogger.jsx

@ -1,4 +1,4 @@
import { getState, setState } from '../../App';
import { getState, setState } from "../../App";
export function pushToMouseLog(logElement) { export function pushToMouseLog(logElement) {
const sensorLog = getState().sensorLog; const sensorLog = getState().sensorLog;
@ -24,7 +24,10 @@ import { getState, setState } from '../../App';
const visitedSitesAmount = sensorLog.visitedSites.length; const visitedSitesAmount = sensorLog.visitedSites.length;
if (visitedSitesAmount > 0) { if (visitedSitesAmount > 0) {
const lastVisitedElement = sensorLog.visitedSites[visitedSitesAmount - 1]; const lastVisitedElement = sensorLog.visitedSites[visitedSitesAmount - 1];
if(lastVisitedElement.path === logElement.path && logElement.timestamp - lastVisitedElement.timestamp < 500) {
if (
lastVisitedElement.path === logElement.path &&
logElement.timestamp - lastVisitedElement.timestamp < 500
) {
return; return;
} }
sensorLog.visitedSites.push(logElement); sensorLog.visitedSites.push(logElement);
@ -43,5 +46,12 @@ import { getState, setState } from '../../App';
} }
export function resetSensorLog() { export function resetSensorLog() {
setState({ sensorLog: {mouseLog: [], clickedElements: [], visitedSites: [], playedSonifications: []}});
setState({
sensorLog: {
mouseLog: [],
clickedElements: [],
visitedSites: [],
playedSonifications: [],
},
});
} }

15
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 { BrowserRouter } from "react-router-dom";
import "./core/i18n/i18n"; 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); pushToClickLog(event);
}; };
const root = ReactDOM.createRoot(document.getElementById('root'));
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render( root.render(
<React.StrictMode> <React.StrictMode>
<BrowserRouter> <BrowserRouter>
<App /> <App />
</BrowserRouter> </BrowserRouter>
</React.StrictMode> </React.StrictMode>
); );

6
src/interfaces/DataComponent.jsx

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

4
src/interfaces/ResetableComponent.jsx

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

3
src/pages/NoPageFound.jsx

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

6
src/pages/TestEndPage.jsx

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

18
src/pages/TestInfoPage.jsx

@ -3,10 +3,22 @@ import InfoPageComponent from "../components/InfoPageComponent";
import RandomIDComponent from "../components/RandomIDComponent"; import RandomIDComponent from "../components/RandomIDComponent";
function TestInfoPage({ redirectLoc }) { 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>
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 /> <RandomIDComponent />
</InfoPageComponent>);
</InfoPageComponent>
);
} }
export default TestInfoPage; export default TestInfoPage;

18
src/pages/TestInfoPage2.jsx

@ -2,9 +2,21 @@ import React from "react";
import InfoPageComponent from "../components/InfoPageComponent"; import InfoPageComponent from "../components/InfoPageComponent";
function TestInfoPage2({ redirectLoc }) { 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>);
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; export default TestInfoPage2;

22
src/pages/TestPage.jsx

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

21
src/pages/TestPage2.jsx

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

28
src/pages/TestQuestionnaire.jsx

@ -1,21 +1,35 @@
import React from "react"; import React from "react";
import { useParams } from 'react-router-dom';
import { useParams } from "react-router-dom";
import QuestionComponent from "../components/questionnaire/QuestionComponent"; import QuestionComponent from "../components/questionnaire/QuestionComponent";
import { useQuestionComponent } from "../components/questionnaire/QuestionnaireData"; import { useQuestionComponent } from "../components/questionnaire/QuestionnaireData";
export default function TestQuestionnaire() { 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 questionProperties = { q1: q1Props, q2: q2Props };
const questionProperty = questionProperties[id]; const questionProperty = questionProperties[id];
return ( return (
<div> <div>
<QuestionComponent {...questionProperty} onUpdateAnswerData={questionProperty.handleUpdateAnswerData} />
<QuestionComponent
{...questionProperty}
onUpdateAnswerData={questionProperty.handleUpdateAnswerData}
/>
<p>Current Answer Data: {JSON.stringify(questionProperty.answerData)}</p> <p>Current Answer Data: {JSON.stringify(questionProperty.answerData)}</p>
</div> </div>
); );

130
src/pages/study_site_1/StartPage1.jsx

@ -1,7 +1,10 @@
import React from "react"; import React from "react";
import { getTranslation } from "../../core/i18n/I18NHandler"; import { getTranslation } from "../../core/i18n/I18NHandler";
import WebpageBanner from "../../components/webpage_container/WebpageBanner"; 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 { pushToMouseLog, getSensorLog } from "../../core/log/SensorLogger";
import { Collapse, Text, Spacer } from "@geist-ui/core"; import { Collapse, Text, Spacer } from "@geist-ui/core";
//import {logo} from "/public/images/budget_bird_logo.png"; //import {logo} from "/public/images/budget_bird_logo.png";
@ -15,67 +18,138 @@ function StartPage1({redirectLoc}) {
// TODO THINK IF I WANT TO JUST USE AN OVERLAY FOR OTHER DUMMY LINKS, WHICH ARENT CORRECT??? // TODO THINK IF I WANT TO JUST USE AN OVERLAY FOR OTHER DUMMY LINKS, WHICH ARENT CORRECT???
// IS THAT OKAY??? // IS THAT OKAY???
return (<StudySite>
return (
<StudySite>
<WebpageBanner translationKey="task_1_info" /> <WebpageBanner translationKey="task_1_info" />
<Spacer h={2} /> <Spacer h={2} />
<img src="/images/budget_bird_logo.png" width="150px" alt="BudgetBird Airlines Logo" />
<img
src="/images/budget_bird_logo.png"
width="150px"
alt="BudgetBird Airlines Logo"
/>
<Spacer h={1} /> <Spacer h={1} />
<h3>Frequently Asked Questions</h3> <h3>Frequently Asked Questions</h3>
<Spacer h={2} /> <Spacer h={2} />
<Collapse.Group> <Collapse.Group>
<Collapse title="Under what conditions can I rebook?"> <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>
<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>
<Collapse title="Can I make a name change after I have booked?"> <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>
<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>
<Collapse title="What are the upper weight limits for free baggage?"> <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 weight limit for your flight can be checked </Text>
<Text> <Text>
The maximum size per piece of baggage is 158 cm (width X + height Y + depth Z).
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> </Text>
<Text>Larger, heavier or additional bags are carried as excess baggage. You can find the prices in our baggage calculator.</Text>
</Collapse> </Collapse>
<Collapse title="What are the passenger rights you have in case of rescheduling or cancellation?"> <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>
<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>
<Collapse title="What costs will incur for rebooking my ticket?"> <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>
<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>
<Collapse title="How do I get my money returned to me after a refund?"> <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>
<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>
<Collapse title="How do I make a reservation?"> <Collapse title="How do I make a reservation?">
<Text>To make a reservation, please contact us in the following way: </Text>
<Text>
To make a reservation, please contact us in the following way:{" "}
</Text>
<Text>Contact Details: </Text> <Text>Contact Details: </Text>
<Text><a href="tel:+31 99 999 9999">+31 99 999 9999</a>
<Text>
<a href="tel:+31 99 999 9999">+31 99 999 9999</a>
<Text>Monday - Sunday open for 24 hours.</Text> <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>
<Text>(Standard phone charges apply.)</Text>
</Text>
<Text>
<a href="mailto:reservation.budgetbird@example.com">
reservation.budgetbird@example.com
</a>
</Text>
</Collapse> </Collapse>
<Collapse title="My flight was cancelled. How do I get my refund?"> <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>
<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>
<Collapse title="How can I contact the Customer Support?"> <Collapse title="How can I contact the Customer Support?">
<Text>Depending on your concern, you can contact Customer Support in the following ways:</Text>
<Text>
Depending on your concern, you can contact Customer Support in the
following ways:
</Text>
<ul> <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>
<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> </ul>
</Collapse> </Collapse>
<Collapse title="How does the BudgetBird Miles program work?"> <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>
<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>
</Collapse.Group> </Collapse.Group>
</StudySite>);
</StudySite>
);
} }
export default StartPage1; export default StartPage1;
Loading…
Cancel
Save