From 9988c82278519e9cf0c239181b7841889fb5a43e Mon Sep 17 00:00:00 2001 From: Ahmedhossamdev Date: Thu, 6 Feb 2025 11:05:25 +0200 Subject: [PATCH 01/24] Add alias for stores directory in Svelte config --- svelte.config.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/svelte.config.js b/svelte.config.js index bc1a7c0..acf72b2 100644 --- a/svelte.config.js +++ b/svelte.config.js @@ -11,7 +11,8 @@ const config = { $config: './src/config', $images: './src/assets/images', $lib: './src/lib', - $src: './src' + $src: './src', + $stores: './src/stores' } }, preprocess: vitePreprocess() From 8f8c2982c17048478c344e9cc5017b4fa1521331 Mon Sep 17 00:00:00 2001 From: Ahmedhossamdev Date: Thu, 6 Feb 2025 11:06:03 +0200 Subject: [PATCH 02/24] Add SurveyModal component --- src/components/surveys/SurveyModal.svelte | 201 ++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 src/components/surveys/SurveyModal.svelte diff --git a/src/components/surveys/SurveyModal.svelte b/src/components/surveys/SurveyModal.svelte new file mode 100644 index 0000000..2ab61a8 --- /dev/null +++ b/src/components/surveys/SurveyModal.svelte @@ -0,0 +1,201 @@ + + +{#if $showSurveyModal && currentSurvey} + +
+

{currentSurvey.name}

+
+ +
+ {#if surveySubmitted} +
+ + + +

Survey Submitted

+

+ Thank you for taking the survey! +

+
+ {:else} +
+ {#if !heroQuestionAnswered && !skipHeroQuestion} + handleInputChange(e, heroQuestion, 0)} + required={heroQuestion?.required} + error={errors[0]} + /> + {:else} +
+ {#each remainingQuestions as question, index} + handleInputChange(e, question, index + 1)} + required={question.required} + error={errors[index + 1]} + /> + {/each} +
+ {/if} +
+ +
+ {#if !heroQuestionAnswered && !skipHeroQuestion} + + {:else} + + + {/if} +
+ {/if} +
+
+{/if} From 6f852ab386116c3988c4ba23b8b9c5defd00b17c Mon Sep 17 00:00:00 2001 From: Ahmedhossamdev Date: Thu, 6 Feb 2025 11:06:28 +0200 Subject: [PATCH 03/24] Add hero question survey functionality to StopPane component --- src/components/stops/StopPane.svelte | 89 +++++++++++++++++++++++++++- 1 file changed, 87 insertions(+), 2 deletions(-) diff --git a/src/components/stops/StopPane.svelte b/src/components/stops/StopPane.svelte index fb0974b..a59083a 100644 --- a/src/components/stops/StopPane.svelte +++ b/src/components/stops/StopPane.svelte @@ -4,10 +4,14 @@ import LoadingSpinner from '$components/LoadingSpinner.svelte'; import Accordion from '$components/containers/SingleSelectAccordion.svelte'; import AccordionItem from '$components/containers/AccordionItem.svelte'; + import SurveyModal from '$components/surveys/SurveyModal.svelte'; + import SurveyQuestion from '$components/surveys/SurveyQuestion.svelte'; import { onDestroy } from 'svelte'; - import '$lib/i18n.js'; import { isLoading, t } from 'svelte-i18n'; + import { submitHeroQuestion, skipSurvey } from '$lib/Surveys/surveyUtils'; + import { surveyStore, showSurveyModal } from '$stores/surveyStore'; + import { getUserId } from '$lib/utils/user'; /** * @typedef {Object} Props @@ -28,6 +32,7 @@ let error = $state(); let interval = null; + let currentStopSurvey = $state(null); async function loadData(stopID) { loading = true; @@ -80,6 +85,49 @@ tripSelected({ detail: data }); handleUpdateRouteMap({ detail: { show } }); } + + let heroAnswer = ''; + let nextSurveyQuestion = $state(false); + let surveyPublicIdentifier = $state(null); + let showHeroQuestion = $state(true); + + async function handleNext() { + if (heroAnswer && heroAnswer.trim() != '') { + showSurveyModal.set(true); + nextSurveyQuestion = true; + + let surveyResponse = { + survey_id: currentStopSurvey.id, + user_identifier: getUserId(), + stop_identifier: stop.id, + stop_latitude: stop.lat, + stop_longitude: stop.lon, + responses: [] + }; + + surveyResponse.responses[0] = { + question_id: currentStopSurvey.questions[0].id, + question_label: currentStopSurvey.questions[0].content.label_text, + question_type: currentStopSurvey.questions[0].content.type, + answer: heroAnswer + }; + + surveyPublicIdentifier = await submitHeroQuestion(surveyResponse); + showHeroQuestion = false; + } + } + + function handleSkip() { + skipSurvey(currentStopSurvey); + showHeroQuestion = false; + } + function handleHeroQuestionChange(event) { + heroAnswer = event.target.value; + } + + $effect(() => { + currentStopSurvey = $surveyStore; + }); {#if $isLoading} @@ -93,7 +141,6 @@ {#if error}

{error}

{/if} - {#if arrivalsAndDepartures}
@@ -114,6 +161,44 @@
+ {#if showHeroQuestion && currentStopSurvey} +
+ +

{currentStopSurvey.name}

+ +
+ +
+
+ {/if} + + {#if nextSurveyQuestion} + + {/if} + {#if arrivalsAndDepartures.arrivalsAndDepartures.length === 0}

{$t('no_arrivals_or_departures_in_next_30_minutes')}

From 917ea7aef548cb4eefb54fdce8ce83bfad06e511 Mon Sep 17 00:00:00 2001 From: Ahmedhossamdev Date: Thu, 6 Feb 2025 11:06:40 +0200 Subject: [PATCH 04/24] Add survey API endpoints for fetching, submitting, and updating survey responses --- src/routes/api/oba/surveys/+server.js | 30 +++++++++++++++++ .../api/oba/surveys/submit-survey/+server.js | 30 +++++++++++++++++ .../oba/surveys/update-survey/[id]/+server.js | 32 +++++++++++++++++++ 3 files changed, 92 insertions(+) create mode 100644 src/routes/api/oba/surveys/+server.js create mode 100644 src/routes/api/oba/surveys/submit-survey/+server.js create mode 100644 src/routes/api/oba/surveys/update-survey/[id]/+server.js diff --git a/src/routes/api/oba/surveys/+server.js b/src/routes/api/oba/surveys/+server.js new file mode 100644 index 0000000..123615a --- /dev/null +++ b/src/routes/api/oba/surveys/+server.js @@ -0,0 +1,30 @@ +import { json } from '@sveltejs/kit'; +import { env } from '$env/dynamic/private'; +import { buildURL } from '$lib/urls.js'; + +const REGION_PATH = `regions/${env.PRIVATE_REGION_ID}/`; + +export async function GET({ url }) { + + const userId = url.searchParams.get('userId'); + + try { + const url = buildURL(env.PRIVATE_OBACO_API_BASE_URL, `${REGION_PATH}surveys.json`, { + user_id: userId + }); + + + const response = await fetch(url); + + if (!response.ok) { + throw new Error('Failed to fetch surveys'); + } + + const data = await response.json(); + + return json(data); + } catch (error) { + console.error('Error loading surveys:', error); + return json({ error: 'Failed to load surveys' }, { status: 500 }); + } +} diff --git a/src/routes/api/oba/surveys/submit-survey/+server.js b/src/routes/api/oba/surveys/submit-survey/+server.js new file mode 100644 index 0000000..741c82a --- /dev/null +++ b/src/routes/api/oba/surveys/submit-survey/+server.js @@ -0,0 +1,30 @@ +import { json } from '@sveltejs/kit'; +import { env } from '$env/dynamic/private'; +import { buildURL } from '$lib/urls.js'; + + +export async function POST({ request }) { + try { + const body = await request.text() + + const url = buildURL(env.PRIVATE_OBACO_API_BASE_URL, '/survey_responses.json'); + + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body + }); + + if (!response.ok) { + throw new Error('Failed to submit survey response'); + } + + const data = await response.json(); + return json(data); + } catch (error) { + console.error('Error submitting survey response:', error); + return json({ error: error.message }, { status: 500 }); + } +} diff --git a/src/routes/api/oba/surveys/update-survey/[id]/+server.js b/src/routes/api/oba/surveys/update-survey/[id]/+server.js new file mode 100644 index 0000000..d556c71 --- /dev/null +++ b/src/routes/api/oba/surveys/update-survey/[id]/+server.js @@ -0,0 +1,32 @@ + +import { json } from '@sveltejs/kit'; +import { env } from '$env/dynamic/private'; +import { buildURL } from '$lib/urls.js'; + + +export async function POST({ request, params }) { + try { + const { id } = params; + const body = await request.text() + + const url = buildURL(env.PRIVATE_OBACO_API_BASE_URL, `/surveys_responses/${id}`); + + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body + }); + + if (!response.ok) { + throw new Error('Failed to update survey response'); + } + + const data = await response.json(); + return json(data); + } catch (error) { + console.error('Error updating survey response:', error); + return json({ error: error.message }, { status: 500 }); + } +} From 2747570c12216c0d1a5128fbfd98f9218b731fd6 Mon Sep 17 00:00:00 2001 From: Ahmedhossamdev Date: Thu, 6 Feb 2025 11:06:48 +0200 Subject: [PATCH 05/24] Update alerts API endpoint to include region path in URL --- src/routes/api/oba/alerts/+server.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/routes/api/oba/alerts/+server.js b/src/routes/api/oba/alerts/+server.js index 5334c25..60598b2 100644 --- a/src/routes/api/oba/alerts/+server.js +++ b/src/routes/api/oba/alerts/+server.js @@ -2,11 +2,14 @@ import GtfsRealtimeBindings from 'gtfs-realtime-bindings'; import { env } from '$env/dynamic/private'; import { buildURL } from '$lib/urls.js'; + +const REGION_PATH = `regions/${env.PRIVATE_REGION_ID}/`; + export async function GET() { try { const alertsURL = buildURL( env.PRIVATE_OBACO_API_BASE_URL, - 'alerts.pb', + REGION_PATH+'alerts.pb', env.PRIVATE_OBACO_SHOW_TEST_ALERTS == 'true' ? { test: 1 } : {} ); From 3dfe06b3e83c40606ed7ef714bdb5204e8ca1548 Mon Sep 17 00:00:00 2001 From: Ahmedhossamdev Date: Thu, 6 Feb 2025 11:07:00 +0200 Subject: [PATCH 06/24] Add survey store --- src/stores/surveyStore.js | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 src/stores/surveyStore.js diff --git a/src/stores/surveyStore.js b/src/stores/surveyStore.js new file mode 100644 index 0000000..0fd3c19 --- /dev/null +++ b/src/stores/surveyStore.js @@ -0,0 +1,6 @@ +import { writable } from 'svelte/store'; + +export const showSurveyModal = writable(false); +export const surveyStore = writable(null); + + From f8f65817fc2c86e0ac2bed09fe99ce0d255442a2 Mon Sep 17 00:00:00 2001 From: Ahmedhossamdev Date: Thu, 6 Feb 2025 11:07:16 +0200 Subject: [PATCH 07/24] Add surveys --- src/routes/+page.svelte | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 21c857b..8027a7d 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -4,13 +4,17 @@ import MapContainer from '$components/MapContainer.svelte'; import RouteModal from '$components/routes/RouteModal.svelte'; import ViewAllRoutesModal from '$components/routes/ViewAllRoutesModal.svelte'; - import { isLoading } from 'svelte-i18n'; + import { isLoading, time } from 'svelte-i18n'; import AlertsModal from '$components/navigation/AlertsModal.svelte'; import { onMount } from 'svelte'; import StopModal from '$components/stops/StopModal.svelte'; import TripPlanModal from '$components/trip-planner/TripPlanModal.svelte'; import { browser } from '$app/environment'; import { PUBLIC_OBA_REGION_NAME } from '$env/static/public'; + import SurveyModal from '$components/surveys/SurveyModal.svelte'; + import { loadSurveys } from '$lib/Surveys/surveyUtils'; + import { showSurveyModal } from '$stores/surveyStore'; + import { getUserId } from '$lib/utils/user'; let stop = $state(); let selectedTrip = $state(null); @@ -47,6 +51,7 @@ stop = stopData; pushState(`/stops/${stop.id}`); showAllRoutesModal = false; + loadSurveys(stop.id, getUserId()); if (currentHighlightedStopId !== null) { mapProvider.unHighlightMarker(currentHighlightedStopId); } @@ -169,7 +174,10 @@ onMount(() => { loadAlerts(); - // close the trip plan modal when the tab is switched + const userId = getUserId(); + + loadSurveys(null, userId); + if (browser) { window.addEventListener('tabSwitched', () => { showTripPlanModal = false; @@ -198,7 +206,6 @@
+ {#if $showSurveyModal} + + {/if} + Date: Thu, 6 Feb 2025 11:07:29 +0200 Subject: [PATCH 08/24] Add SurveyQuestion component --- src/components/surveys/SurveyQuestion.svelte | 99 ++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 src/components/surveys/SurveyQuestion.svelte diff --git a/src/components/surveys/SurveyQuestion.svelte b/src/components/surveys/SurveyQuestion.svelte new file mode 100644 index 0000000..75fedc6 --- /dev/null +++ b/src/components/surveys/SurveyQuestion.svelte @@ -0,0 +1,99 @@ + + + + +{#if question.content.type === 'text'} + +{:else if question.content.type === 'radio'} +
+ {#each question.content.options as option} + + {option} + + {/each} +
+{:else if question.content.type === 'checkbox'} +
+ {#each question.content.options as option} + + {option} + + {/each} +
+{:else if question.content.type === 'external_survey'} +
+ + {question.content.label_text} + + {#if question.content.survey_provider} +

Powered by {question.content.survey_provider}

+ {/if} +
+{/if} + +{#if error} +

This question is required.

+{/if} From 5b6d18661d20e0fa23600f62e9c550ca6119d6e2 Mon Sep 17 00:00:00 2001 From: Ahmedhossamdev Date: Thu, 6 Feb 2025 11:07:47 +0200 Subject: [PATCH 09/24] Add survey utility functions for loading and managing survey responses --- src/lib/Surveys/surveyUtils.js | 123 +++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 src/lib/Surveys/surveyUtils.js diff --git a/src/lib/Surveys/surveyUtils.js b/src/lib/Surveys/surveyUtils.js new file mode 100644 index 0000000..98eae87 --- /dev/null +++ b/src/lib/Surveys/surveyUtils.js @@ -0,0 +1,123 @@ + +import { showSurveyModal, surveyStore } from '$stores/surveyStore.js'; + + + +export async function loadSurveys(stopId = null, userId = null) { + try { + const response = await fetch(`/api/oba/surveys?userId=${userId}`); + if (!response.ok) throw new Error('Failed to fetch surveys'); + + const data = await response.json(); + const validSurveys = getValidSurveys(data.surveys); + + let selectedSurvey = null; + + if (stopId) { + selectedSurvey = getValidStopSurvey(validSurveys, stopId) || getShowSurveyOnAllStops(validSurveys); + } else { + selectedSurvey = getMapSurvey(validSurveys); + } + + surveyStore.set(selectedSurvey); + + showSurveyModal.set(selectedSurvey?.show_on_map === true ); + + } catch (error) { + console.error('Error loading surveys:', error); + } +} + +export function getValidSurveys(surveys) { + const now = new Date(); + return surveys.filter(survey => + new Date(survey.end_date) > now && + !localStorage.getItem(`survey_${survey.id}_answered`) && + !localStorage.getItem(`survey_${survey.id}_skipped`) + ); +} + +export function getValidStopSurvey(surveys, stopId) { + return surveys.find(survey => + survey.show_on_stops && + survey.visible_stop_list?.includes(stopId) + ) || null; +} + +export function getShowSurveyOnAllStops(surveys) { + return surveys.find(survey => + survey.show_on_stops && + survey.visible_stop_list === null + ) || null; +} + +export function getMapSurvey(surveys) { + return surveys.find(survey => survey.show_on_map) || null; +} + +export async function submitHeroQuestion(surveyResponse) { + + try { + const payload = { + ...surveyResponse, + responses: JSON.stringify([surveyResponse.responses[0]]) + }; + const response = await fetch('/api/oba/surveys/submit-survey', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json' + }, + body: JSON.stringify(payload) + }); + if (!response.ok) { + throw new Error('Failed to submit survey response', response); + } + const data = await response.json(); + return data.survey_response.id; + } catch (error) { + console.error('Error submitting hero question:', error); + throw error; + } +} + + +export async function updateSurveyResponse(surveyPublicIdentifier, surveyResponse) { + try { + const payload = { + ...surveyResponse, + responses: JSON.stringify(surveyResponse.responses) + }; + const response = await fetch(`/api/oba/surveys/update-survey/${surveyPublicIdentifier}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json' + }, + body: JSON.stringify(payload) + }); + if (!response.ok) { + throw new Error('Failed to update survey response'); + } + return true; + } catch (error) { + console.error('Error updating survey response:', error); + throw error; + } +} + + +export function submitSurvey(survey, hideSurveyModal) { + localStorage.setItem(`survey_${survey.id}_answered`, true); + if (hideSurveyModal) { + setTimeout(() => { + showSurveyModal.set(false); + }, 3000); + } +} + + +export function skipSurvey(survey) { + localStorage.setItem(`survey_${survey.id}_skipped`, true); + showSurveyModal.set(false); +} From 7e83ae9dc4128b734ddc776507b5e6c7a5746fa7 Mon Sep 17 00:00:00 2001 From: Ahmedhossamdev Date: Thu, 6 Feb 2025 11:07:56 +0200 Subject: [PATCH 10/24] Add user utility functions for managing user ID cookies --- src/lib/utils/user.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/lib/utils/user.js diff --git a/src/lib/utils/user.js b/src/lib/utils/user.js new file mode 100644 index 0000000..5cf8a4b --- /dev/null +++ b/src/lib/utils/user.js @@ -0,0 +1,19 @@ +import Controls from "flowbite-svelte/Controls.svelte"; + +export function getCookie(name) { + const value = `; ${document.cookie}`; + const parts = value.split(`; ${name}=`); + if (parts.length === 2) return parts.pop().split(';').shift(); +} + +export function getUserId() { + let userId = getCookie('userId'); + if (!userId) { + userId = crypto.randomUUID(); + + + //! Set cookie for 1 year + document.cookie = `userId=${userId}; path=/; max-age=${60 * 60 * 24 * 365}`; + } + return userId; +} From 1c497419c9aebe3bafb04ac1aa7713e058745b3a Mon Sep 17 00:00:00 2001 From: Ahmedhossamdev Date: Thu, 6 Feb 2025 21:36:07 +0200 Subject: [PATCH 11/24] Update .env.example to include region ID for OneBusAway API --- .env.example | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.env.example b/.env.example index f2bb6c1..0e34306 100644 --- a/.env.example +++ b/.env.example @@ -3,7 +3,8 @@ PRIVATE_OBA_API_KEY="test" PRIVATE_OBA_GEOCODER_API_KEY="" PRIVATE_OBA_GEOCODER_PROVIDER="google" -PRIVATE_OBACO_API_BASE_URL=https://onebusaway.co/api/v1/regions/:REGION_ID +PRIVATE_OBACO_API_BASE_URL=https://onebusaway.co/api/v1 +PRIVATE_REGION_ID= PRIVATE_OBACO_SHOW_TEST_ALERTS=false PUBLIC_NAV_BAR_LINKS={"Home": "/","About": "/about","Contact": "/contact","Fares & Tolls": "/fares-and-tolls"} From bac3d5e59a91ba3d727ba22c9d1d396585ac2d4a Mon Sep 17 00:00:00 2001 From: Ahmedhossamdev Date: Thu, 6 Feb 2025 21:41:36 +0200 Subject: [PATCH 12/24] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e3a2d04..1b21e60 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,8 @@ See `.env.example` for an example of the required keys and values. - `PUBLIC_OBA_REGION_CENTER_LAT` - float: (required) The region's center latitude. - `PUBLIC_OBA_REGION_CENTER_LNG` - float: (required) The region's center longitude. - `PRIVATE_OBA_API_KEY` - string: (required) Your OneBusAway REST API server key. -- `PRIVATE_OBACO_API_BASE_URL` - string: (optional) Your OneBusAway.co server base URL, including the path prefix `/api/v1/regions/`. +- `PRIVATE_OBACO_API_BASE_URL` - string: (optional) Your OneBusAway.co server base URL, including the path prefix `/api/v1. +- `PRIVATE_REGION_ID` - string: (required if OBACO_API_BASE_URL provided). - `PRIVATE_OBACO_SHOW_TEST_ALERTS` - boolean: (optional) Show test alerts on the website. Don't set this value in production. ### Maps From 2dc3531d0071723c2cb160364bdb92b40878c722 Mon Sep 17 00:00:00 2001 From: Ahmedhossamdev Date: Thu, 6 Feb 2025 21:42:55 +0200 Subject: [PATCH 13/24] Refactorfunctions to use 'stop' parameter instead of stopId and add filter for routes --- src/lib/Surveys/surveyUtils.js | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/lib/Surveys/surveyUtils.js b/src/lib/Surveys/surveyUtils.js index 98eae87..a10d5db 100644 --- a/src/lib/Surveys/surveyUtils.js +++ b/src/lib/Surveys/surveyUtils.js @@ -3,7 +3,7 @@ import { showSurveyModal, surveyStore } from '$stores/surveyStore.js'; -export async function loadSurveys(stopId = null, userId = null) { +export async function loadSurveys(stop = null, userId = null) { try { const response = await fetch(`/api/oba/surveys?userId=${userId}`); if (!response.ok) throw new Error('Failed to fetch surveys'); @@ -13,8 +13,8 @@ export async function loadSurveys(stopId = null, userId = null) { let selectedSurvey = null; - if (stopId) { - selectedSurvey = getValidStopSurvey(validSurveys, stopId) || getShowSurveyOnAllStops(validSurveys); + if (stop) { + selectedSurvey = getValidStopSurvey(validSurveys, stop) || getShowSurveyOnAllStops(validSurveys); } else { selectedSurvey = getMapSurvey(validSurveys); } @@ -37,13 +37,20 @@ export function getValidSurveys(surveys) { ); } -export function getValidStopSurvey(surveys, stopId) { - return surveys.find(survey => - survey.show_on_stops && - survey.visible_stop_list?.includes(stopId) - ) || null; +export function getValidStopSurvey(surveys, stop) { + + return surveys.find(survey => + survey.show_on_stops && + ( + (survey.visible_stop_list && survey.visible_stop_list.includes(stop.id)) || + survey.visible_route_list !== null && + survey.visible_route_list.some(routeId => stop.routeIds.includes(routeId))) + + ) || null; } + + export function getShowSurveyOnAllStops(surveys) { return surveys.find(survey => survey.show_on_stops && From a3402d08048109ee16b860828c182c5464a86bf0 Mon Sep 17 00:00:00 2001 From: Ahmedhossamdev Date: Thu, 6 Feb 2025 21:43:05 +0200 Subject: [PATCH 14/24] Update loadSurveys function to use 'stop' parameter instead of stop.id --- src/routes/+page.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 8027a7d..ac40e23 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -51,7 +51,7 @@ stop = stopData; pushState(`/stops/${stop.id}`); showAllRoutesModal = false; - loadSurveys(stop.id, getUserId()); + loadSurveys(stop, getUserId()); if (currentHighlightedStopId !== null) { mapProvider.unHighlightMarker(currentHighlightedStopId); } From 7c241f0dcdf436ee41a5c6689f7438f36c137ed8 Mon Sep 17 00:00:00 2001 From: Ahmedhossamdev Date: Thu, 6 Feb 2025 21:43:12 +0200 Subject: [PATCH 15/24] Add onMount lifecycle to load surveys using stop and user ID --- src/routes/stops/[stopID]/+page.svelte | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/routes/stops/[stopID]/+page.svelte b/src/routes/stops/[stopID]/+page.svelte index ed4cd56..3053a75 100644 --- a/src/routes/stops/[stopID]/+page.svelte +++ b/src/routes/stops/[stopID]/+page.svelte @@ -4,10 +4,17 @@ import StandalonePage from '$components/StandalonePage.svelte'; import '$lib/i18n.js'; import { t } from 'svelte-i18n'; + import { onMount } from 'svelte'; + import { loadSurveys } from '$lib/Surveys/surveyUtils.js'; + import { getUserId } from '$lib/utils/user.js'; let { data } = $props(); const stop = data.stopData.entry; const arrivalsAndDeparturesResponse = data.arrivalsAndDeparturesResponse; + + onMount(() => { + loadSurveys(stop, getUserId()); + }); From 6da2b3adc04913bdae4a336adb1aa31e574ef26c Mon Sep 17 00:00:00 2001 From: Ahmedhossamdev Date: Thu, 6 Feb 2025 21:47:56 +0200 Subject: [PATCH 16/24] Linting and formatting --- src/lib/Surveys/surveyUtils.js | 179 +++++++++--------- src/lib/utils/user.js | 23 +-- src/routes/api/oba/alerts/+server.js | 3 +- src/routes/api/oba/surveys/+server.js | 32 ++-- .../api/oba/surveys/submit-survey/+server.js | 39 ++-- .../oba/surveys/update-survey/[id]/+server.js | 42 ++-- src/stores/surveyStore.js | 2 - 7 files changed, 150 insertions(+), 170 deletions(-) diff --git a/src/lib/Surveys/surveyUtils.js b/src/lib/Surveys/surveyUtils.js index a10d5db..5159471 100644 --- a/src/lib/Surveys/surveyUtils.js +++ b/src/lib/Surveys/surveyUtils.js @@ -1,130 +1,121 @@ - import { showSurveyModal, surveyStore } from '$stores/surveyStore.js'; - - export async function loadSurveys(stop = null, userId = null) { - try { - const response = await fetch(`/api/oba/surveys?userId=${userId}`); - if (!response.ok) throw new Error('Failed to fetch surveys'); + try { + const response = await fetch(`/api/oba/surveys?userId=${userId}`); + if (!response.ok) throw new Error('Failed to fetch surveys'); - const data = await response.json(); - const validSurveys = getValidSurveys(data.surveys); + const data = await response.json(); + const validSurveys = getValidSurveys(data.surveys); - let selectedSurvey = null; + let selectedSurvey = null; - if (stop) { - selectedSurvey = getValidStopSurvey(validSurveys, stop) || getShowSurveyOnAllStops(validSurveys); - } else { - selectedSurvey = getMapSurvey(validSurveys); - } + if (stop) { + selectedSurvey = + getValidStopSurvey(validSurveys, stop) || getShowSurveyOnAllStops(validSurveys); + } else { + selectedSurvey = getMapSurvey(validSurveys); + } - surveyStore.set(selectedSurvey); + surveyStore.set(selectedSurvey); - showSurveyModal.set(selectedSurvey?.show_on_map === true ); - - } catch (error) { - console.error('Error loading surveys:', error); - } + showSurveyModal.set(selectedSurvey?.show_on_map === true); + } catch (error) { + console.error('Error loading surveys:', error); + } } export function getValidSurveys(surveys) { - const now = new Date(); - return surveys.filter(survey => - new Date(survey.end_date) > now && - !localStorage.getItem(`survey_${survey.id}_answered`) && - !localStorage.getItem(`survey_${survey.id}_skipped`) - ); + const now = new Date(); + return surveys.filter( + (survey) => + new Date(survey.end_date) > now && + !localStorage.getItem(`survey_${survey.id}_answered`) && + !localStorage.getItem(`survey_${survey.id}_skipped`) + ); } export function getValidStopSurvey(surveys, stop) { - - return surveys.find(survey => - survey.show_on_stops && - ( - (survey.visible_stop_list && survey.visible_stop_list.includes(stop.id)) || - survey.visible_route_list !== null && - survey.visible_route_list.some(routeId => stop.routeIds.includes(routeId))) - - ) || null; + return ( + surveys.find( + (survey) => + survey.show_on_stops && + ((survey.visible_stop_list && survey.visible_stop_list.includes(stop.id)) || + (survey.visible_route_list !== null && + survey.visible_route_list.some((routeId) => stop.routeIds.includes(routeId)))) + ) || null + ); } - - export function getShowSurveyOnAllStops(surveys) { - return surveys.find(survey => - survey.show_on_stops && - survey.visible_stop_list === null - ) || null; + return ( + surveys.find((survey) => survey.show_on_stops && survey.visible_stop_list === null) || null + ); } export function getMapSurvey(surveys) { - return surveys.find(survey => survey.show_on_map) || null; + return surveys.find((survey) => survey.show_on_map) || null; } export async function submitHeroQuestion(surveyResponse) { - - try { - const payload = { - ...surveyResponse, - responses: JSON.stringify([surveyResponse.responses[0]]) - }; - const response = await fetch('/api/oba/surveys/submit-survey', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - Accept: 'application/json' - }, - body: JSON.stringify(payload) - }); - if (!response.ok) { - throw new Error('Failed to submit survey response', response); - } - const data = await response.json(); - return data.survey_response.id; - } catch (error) { - console.error('Error submitting hero question:', error); - throw error; - } + try { + const payload = { + ...surveyResponse, + responses: JSON.stringify([surveyResponse.responses[0]]) + }; + const response = await fetch('/api/oba/surveys/submit-survey', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json' + }, + body: JSON.stringify(payload) + }); + if (!response.ok) { + throw new Error('Failed to submit survey response', response); + } + const data = await response.json(); + return data.survey_response.id; + } catch (error) { + console.error('Error submitting hero question:', error); + throw error; + } } - export async function updateSurveyResponse(surveyPublicIdentifier, surveyResponse) { - try { - const payload = { - ...surveyResponse, - responses: JSON.stringify(surveyResponse.responses) - }; - const response = await fetch(`/api/oba/surveys/update-survey/${surveyPublicIdentifier}`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - Accept: 'application/json' - }, - body: JSON.stringify(payload) - }); - if (!response.ok) { - throw new Error('Failed to update survey response'); - } - return true; - } catch (error) { - console.error('Error updating survey response:', error); - throw error; - } + try { + const payload = { + ...surveyResponse, + responses: JSON.stringify(surveyResponse.responses) + }; + const response = await fetch(`/api/oba/surveys/update-survey/${surveyPublicIdentifier}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json' + }, + body: JSON.stringify(payload) + }); + if (!response.ok) { + throw new Error('Failed to update survey response'); + } + return true; + } catch (error) { + console.error('Error updating survey response:', error); + throw error; + } } - export function submitSurvey(survey, hideSurveyModal) { localStorage.setItem(`survey_${survey.id}_answered`, true); - if (hideSurveyModal) { - setTimeout(() => { + if (hideSurveyModal) { + setTimeout(() => { showSurveyModal.set(false); }, 3000); - } + } } - export function skipSurvey(survey) { - localStorage.setItem(`survey_${survey.id}_skipped`, true); - showSurveyModal.set(false); + localStorage.setItem(`survey_${survey.id}_skipped`, true); + showSurveyModal.set(false); } diff --git a/src/lib/utils/user.js b/src/lib/utils/user.js index 5cf8a4b..41a49f4 100644 --- a/src/lib/utils/user.js +++ b/src/lib/utils/user.js @@ -1,19 +1,16 @@ -import Controls from "flowbite-svelte/Controls.svelte"; - export function getCookie(name) { - const value = `; ${document.cookie}`; - const parts = value.split(`; ${name}=`); - if (parts.length === 2) return parts.pop().split(';').shift(); + const value = `; ${document.cookie}`; + const parts = value.split(`; ${name}=`); + if (parts.length === 2) return parts.pop().split(';').shift(); } export function getUserId() { - let userId = getCookie('userId'); - if (!userId) { - userId = crypto.randomUUID(); - + let userId = getCookie('userId'); + if (!userId) { + userId = crypto.randomUUID(); - //! Set cookie for 1 year - document.cookie = `userId=${userId}; path=/; max-age=${60 * 60 * 24 * 365}`; - } - return userId; + //! Set cookie for 1 year + document.cookie = `userId=${userId}; path=/; max-age=${60 * 60 * 24 * 365}`; + } + return userId; } diff --git a/src/routes/api/oba/alerts/+server.js b/src/routes/api/oba/alerts/+server.js index 60598b2..043928c 100644 --- a/src/routes/api/oba/alerts/+server.js +++ b/src/routes/api/oba/alerts/+server.js @@ -2,14 +2,13 @@ import GtfsRealtimeBindings from 'gtfs-realtime-bindings'; import { env } from '$env/dynamic/private'; import { buildURL } from '$lib/urls.js'; - const REGION_PATH = `regions/${env.PRIVATE_REGION_ID}/`; export async function GET() { try { const alertsURL = buildURL( env.PRIVATE_OBACO_API_BASE_URL, - REGION_PATH+'alerts.pb', + REGION_PATH + 'alerts.pb', env.PRIVATE_OBACO_SHOW_TEST_ALERTS == 'true' ? { test: 1 } : {} ); diff --git a/src/routes/api/oba/surveys/+server.js b/src/routes/api/oba/surveys/+server.js index 123615a..f110fbc 100644 --- a/src/routes/api/oba/surveys/+server.js +++ b/src/routes/api/oba/surveys/+server.js @@ -5,26 +5,24 @@ import { buildURL } from '$lib/urls.js'; const REGION_PATH = `regions/${env.PRIVATE_REGION_ID}/`; export async function GET({ url }) { + const userId = url.searchParams.get('userId'); - const userId = url.searchParams.get('userId'); + try { + const url = buildURL(env.PRIVATE_OBACO_API_BASE_URL, `${REGION_PATH}surveys.json`, { + user_id: userId + }); - try { - const url = buildURL(env.PRIVATE_OBACO_API_BASE_URL, `${REGION_PATH}surveys.json`, { - user_id: userId - }); + const response = await fetch(url); + if (!response.ok) { + throw new Error('Failed to fetch surveys'); + } - const response = await fetch(url); + const data = await response.json(); - if (!response.ok) { - throw new Error('Failed to fetch surveys'); - } - - const data = await response.json(); - - return json(data); - } catch (error) { - console.error('Error loading surveys:', error); - return json({ error: 'Failed to load surveys' }, { status: 500 }); - } + return json(data); + } catch (error) { + console.error('Error loading surveys:', error); + return json({ error: 'Failed to load surveys' }, { status: 500 }); + } } diff --git a/src/routes/api/oba/surveys/submit-survey/+server.js b/src/routes/api/oba/surveys/submit-survey/+server.js index 741c82a..f19a47c 100644 --- a/src/routes/api/oba/surveys/submit-survey/+server.js +++ b/src/routes/api/oba/surveys/submit-survey/+server.js @@ -2,29 +2,28 @@ import { json } from '@sveltejs/kit'; import { env } from '$env/dynamic/private'; import { buildURL } from '$lib/urls.js'; - export async function POST({ request }) { - try { - const body = await request.text() + try { + const body = await request.text(); - const url = buildURL(env.PRIVATE_OBACO_API_BASE_URL, '/survey_responses.json'); + const url = buildURL(env.PRIVATE_OBACO_API_BASE_URL, '/survey_responses.json'); - const response = await fetch(url, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body - }); + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body + }); - if (!response.ok) { - throw new Error('Failed to submit survey response'); - } + if (!response.ok) { + throw new Error('Failed to submit survey response'); + } - const data = await response.json(); - return json(data); - } catch (error) { - console.error('Error submitting survey response:', error); - return json({ error: error.message }, { status: 500 }); - } + const data = await response.json(); + return json(data); + } catch (error) { + console.error('Error submitting survey response:', error); + return json({ error: error.message }, { status: 500 }); + } } diff --git a/src/routes/api/oba/surveys/update-survey/[id]/+server.js b/src/routes/api/oba/surveys/update-survey/[id]/+server.js index d556c71..3012ca4 100644 --- a/src/routes/api/oba/surveys/update-survey/[id]/+server.js +++ b/src/routes/api/oba/surveys/update-survey/[id]/+server.js @@ -1,32 +1,30 @@ - import { json } from '@sveltejs/kit'; import { env } from '$env/dynamic/private'; import { buildURL } from '$lib/urls.js'; - export async function POST({ request, params }) { - try { - const { id } = params; - const body = await request.text() + try { + const { id } = params; + const body = await request.text(); - const url = buildURL(env.PRIVATE_OBACO_API_BASE_URL, `/surveys_responses/${id}`); + const url = buildURL(env.PRIVATE_OBACO_API_BASE_URL, `/surveys_responses/${id}`); - const response = await fetch(url, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body - }); + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body + }); - if (!response.ok) { - throw new Error('Failed to update survey response'); - } + if (!response.ok) { + throw new Error('Failed to update survey response'); + } - const data = await response.json(); - return json(data); - } catch (error) { - console.error('Error updating survey response:', error); - return json({ error: error.message }, { status: 500 }); - } + const data = await response.json(); + return json(data); + } catch (error) { + console.error('Error updating survey response:', error); + return json({ error: error.message }, { status: 500 }); + } } diff --git a/src/stores/surveyStore.js b/src/stores/surveyStore.js index 0fd3c19..e467eb8 100644 --- a/src/stores/surveyStore.js +++ b/src/stores/surveyStore.js @@ -2,5 +2,3 @@ import { writable } from 'svelte/store'; export const showSurveyModal = writable(false); export const surveyStore = writable(null); - - From ab08be3ed68b173865acbd1fc03a86040dbf54cb Mon Sep 17 00:00:00 2001 From: Ahmedhossamdev Date: Thu, 6 Feb 2025 21:50:27 +0200 Subject: [PATCH 17/24] Remove unused 'time' import from +page.svelte --- src/routes/+page.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index ac40e23..dc0dc07 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -4,7 +4,7 @@ import MapContainer from '$components/MapContainer.svelte'; import RouteModal from '$components/routes/RouteModal.svelte'; import ViewAllRoutesModal from '$components/routes/ViewAllRoutesModal.svelte'; - import { isLoading, time } from 'svelte-i18n'; + import { isLoading } from 'svelte-i18n'; import AlertsModal from '$components/navigation/AlertsModal.svelte'; import { onMount } from 'svelte'; import StopModal from '$components/stops/StopModal.svelte'; From a6e1215c3755dbbb22061068c1930aae67596009 Mon Sep 17 00:00:00 2001 From: Ahmedhossamdev Date: Thu, 6 Feb 2025 23:10:34 +0200 Subject: [PATCH 18/24] Fix typo --- src/routes/api/oba/surveys/update-survey/[id]/+server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/api/oba/surveys/update-survey/[id]/+server.js b/src/routes/api/oba/surveys/update-survey/[id]/+server.js index 3012ca4..e2d98e2 100644 --- a/src/routes/api/oba/surveys/update-survey/[id]/+server.js +++ b/src/routes/api/oba/surveys/update-survey/[id]/+server.js @@ -7,7 +7,7 @@ export async function POST({ request, params }) { const { id } = params; const body = await request.text(); - const url = buildURL(env.PRIVATE_OBACO_API_BASE_URL, `/surveys_responses/${id}`); + const url = buildURL(env.PRIVATE_OBACO_API_BASE_URL, `/survey_responses/${id}`); const response = await fetch(url, { method: 'POST', From c1a67cfac5b499ab674efe3551d7a7f62521a455 Mon Sep 17 00:00:00 2001 From: Ahmedhossamdev Date: Thu, 6 Feb 2025 23:20:42 +0200 Subject: [PATCH 19/24] Rename surveyPublicIdentifierOutside to surveyPublicId --- src/components/stops/StopPane.svelte | 2 +- src/components/surveys/SurveyModal.svelte | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/stops/StopPane.svelte b/src/components/stops/StopPane.svelte index a59083a..47fa2a5 100644 --- a/src/components/stops/StopPane.svelte +++ b/src/components/stops/StopPane.svelte @@ -195,7 +195,7 @@ currentSurvey={currentStopSurvey} {stop} skipHeroQuestion={true} - surveyPublicIdentifierOutside={surveyPublicIdentifier} + surveyPublicId={surveyPublicIdentifier} /> {/if} diff --git a/src/components/surveys/SurveyModal.svelte b/src/components/surveys/SurveyModal.svelte index 2ab61a8..8c1ef12 100644 --- a/src/components/surveys/SurveyModal.svelte +++ b/src/components/surveys/SurveyModal.svelte @@ -11,7 +11,7 @@ import { showSurveyModal, surveyStore } from '$stores/surveyStore'; import { getUserId } from '$lib/utils/user'; - let { stop = $bindable(null), skipHeroQuestion, surveyPublicIdentifierOutside } = $props(); + let { stop = $bindable(null), skipHeroQuestion, surveyPublicId } = $props(); let userAnswers = $state([]); let heroQuestionAnswered = $state(false); @@ -97,7 +97,7 @@ } async function updateSurveyResponse() { - if (surveyPublicIdentifierOutside) [(surveyPublicIdentifier = surveyPublicIdentifierOutside)]; + if (surveyPublicId) [(surveyPublicIdentifier = surveyPublicId)]; updateSurveyResponseUtil(surveyPublicIdentifier, surveyResponse); } From d268e812e77267132792aab3327c289e96ff139c Mon Sep 17 00:00:00 2001 From: Ahmedhossamdev Date: Sat, 8 Feb 2025 07:27:04 +0200 Subject: [PATCH 20/24] Add unit tests for survey utility functions --- src/tests/lib/surveysUtils.test.js | 215 +++++++++++++++++++++++++++++ 1 file changed, 215 insertions(+) create mode 100644 src/tests/lib/surveysUtils.test.js diff --git a/src/tests/lib/surveysUtils.test.js b/src/tests/lib/surveysUtils.test.js new file mode 100644 index 0000000..48220b9 --- /dev/null +++ b/src/tests/lib/surveysUtils.test.js @@ -0,0 +1,215 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { getValidSurveys, getValidStopSurvey, getShowSurveyOnAllStops, getMapSurvey, submitHeroQuestion, updateSurveyResponse } from '../../lib/Surveys/surveyUtils'; + +beforeEach(() => { + let store = {}; + vi.stubGlobal("localStorage", { + getItem: vi.fn((key) => store[key] || null), + setItem: vi.fn((key, value) => { + store[key] = value; + }), + removeItem: vi.fn((key) => { + delete store[key]; + }), + clear: vi.fn(() => { + store = {}; + }), + }); + + localStorage.clear(); +}); + + +describe('getValidSurveys', () => { + it('should return surveys with valid end_date or no end_date', () => { + const now = new Date(); + const futureDate = new Date(now.getTime() + 10000).toISOString(); + const pastDate = new Date(now.getTime() - 10000).toISOString(); + + const surveys = [ + { id: 1, end_date: futureDate }, + { id: 2, end_date: pastDate }, + { id: 3 } // case when end_date is not present, the survey should be valid also. + ]; + + const valid = getValidSurveys(surveys); + expect(valid).toEqual([ + { id: 1, end_date: futureDate }, + { id: 3 } + ]); + }); + + it('should filter out surveys that were answered or skipped', () => { + const now = new Date(); + const futureDate = new Date(now.getTime() + 10000).toISOString(); + + const surveys = [ + { id: 1, end_date: futureDate }, + { id: 2, end_date: futureDate } + ]; + + localStorage.setItem('survey_1_answered', 'true'); + localStorage.setItem('survey_2_skipped', 'true'); + + const valid = getValidSurveys(surveys); + expect(valid).toEqual([]); + }); +}); + +describe('getValidStopSurvey', () => { + it('should return a survey based on visible_stop_list matching stop id', () => { + const surveys = [ + { id: 1, show_on_stops: false }, + { id: 2, show_on_stops: true, visible_stop_list: ['stop1'], visible_route_list: null }, + { id: 3, show_on_stops: true, visible_stop_list: ['stop2'], visible_route_list: null } + ]; + const stop = { id: 'stop1', routeIds: [] }; + + const result = getValidStopSurvey(surveys, stop); + expect(result).toEqual(surveys[1]); + }); + + it('should return a survey based on visible_route_list matching one of stop.routeIds', () => { + const surveys = [ + { id: 1, show_on_stops: true, visible_stop_list: null, visible_route_list: ['r1', 'r2'] }, + { id: 2, show_on_stops: true, visible_stop_list: null, visible_route_list: ['r3'] } + ]; + const stop = { id: 'stop10', routeIds: ['r2'] }; + + const result = getValidStopSurvey(surveys, stop); + expect(result).toEqual(surveys[0]); + }); + + it('should return null if no survey matches', () => { + const surveys = [ + { id: 1, show_on_stops: true, visible_stop_list: ['stop3'], visible_route_list: ['r5'] } + ]; + const stop = { id: 'stop1', routeIds: ['r2'] }; + + const result = getValidStopSurvey(surveys, stop); + expect(result).toBeNull(); + }); +}); + +describe('getShowSurveyOnAllStops', () => { + it('should return survey if show_on_stops is true and visible_stop_list is null', () => { + const surveys = [ + { id: 1, show_on_stops: true, visible_stop_list: null }, + { id: 2, show_on_stops: true, visible_stop_list: ['stop1'] } + ]; + + const result = getShowSurveyOnAllStops(surveys); + expect(result).toEqual(surveys[0]); + }); + + it('should return null if no survey meets the criteria', () => { + const surveys = [ + { id: 1, show_on_stops: false, visible_stop_list: null }, + { id: 2, show_on_stops: true, visible_stop_list: ['stop1'] } + ]; + + const result = getShowSurveyOnAllStops(surveys); + expect(result).toBeNull(); + }); +}); + +describe('getMapSurvey', () => { + it('should return the survey with show_on_map true', () => { + const surveys = [ + { id: 1, show_on_map: false }, + { id: 2, show_on_map: true } + ]; + + const result = getMapSurvey(surveys); + expect(result).toEqual(surveys[1]); + }); + + it('should return null if no survey has show_on_map true', () => { + const surveys = [ + { id: 1, show_on_map: false }, + { id: 2, show_on_map: false } + ]; + + const result = getMapSurvey(surveys); + expect(result).toBeNull(); + }); +}); + +describe('submitHeroQuestion', () => { + beforeEach(() => { + global.fetch = vi.fn(); + }); + + afterEach(() => { + vi.resetAllMocks(); + }); + + it('should resolve with data when response is ok', async () => { +const mockResponseData = { survey_response: { id: "1" } }; + global.fetch.mockResolvedValue({ + ok: true, + json: async () => mockResponseData, + }); + + const surveyResponse = { id: 1, user_identifier: 'user123', stop_identifier: 'stop456', + "responses": "[{ question_id: 1, question_label: 'Question', question_type: 'radio', answer: 'yes' }]" }; + const result = await submitHeroQuestion(surveyResponse); + + expect(result).toEqual(mockResponseData.survey_response.id); + expect(global.fetch).toHaveBeenCalled(); + }); + + it('should throw an error when response is not ok', async () => { + global.fetch.mockResolvedValue({ + ok: false, + }); + +await expect(submitHeroQuestion({})).rejects.toThrow(expect.any(Error)); + }); +}); + + +describe('updateSurveyResponse', () => { + beforeEach(() => { + global.fetch = vi.fn(); + }); + + afterEach(() => { + vi.resetAllMocks(); + }); + + it('should resolve with true when response is ok', async () => { + global.fetch.mockResolvedValue({ + ok: true, + }); + + const surveyPublicIdentifier = "abc123"; + const surveyResponse = { + id: 1, + user_identifier: 'user123', + stop_identifier: 'stop456', + responses: [{ question_id: 1, answer: 'updated answer' }] + }; + + const result = await updateSurveyResponse(surveyPublicIdentifier, surveyResponse); + + expect(result).toEqual(true); + }); + + it('should throw an error when response is not ok', async () => { + global.fetch.mockResolvedValue({ + ok: false, + }); + + const surveyPublicIdentifier = "abc123"; + const surveyResponse = { + id: 1, + user_identifier: 'user123', + stop_identifier: 'stop456', + responses: [{ question_id: 1, answer: 'updated answer' }] + }; + + await expect(updateSurveyResponse(surveyPublicIdentifier, surveyResponse)) + .rejects.toThrow('Failed to update survey response'); + }); +}); From cd9acd3f33004008424fd5900e017f939b6ba1ef Mon Sep 17 00:00:00 2001 From: Ahmedhossamdev Date: Sat, 8 Feb 2025 07:27:25 +0200 Subject: [PATCH 21/24] Refactor survey utility functions for improved readability --- src/lib/Surveys/surveyUtils.js | 47 ++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/src/lib/Surveys/surveyUtils.js b/src/lib/Surveys/surveyUtils.js index 5159471..8e8ab7c 100644 --- a/src/lib/Surveys/surveyUtils.js +++ b/src/lib/Surveys/surveyUtils.js @@ -26,27 +26,40 @@ export async function loadSurveys(stop = null, userId = null) { } export function getValidSurveys(surveys) { - const now = new Date(); - return surveys.filter( - (survey) => - new Date(survey.end_date) > now && - !localStorage.getItem(`survey_${survey.id}_answered`) && - !localStorage.getItem(`survey_${survey.id}_skipped`) - ); + const now = new Date(); + return surveys.filter((survey) => { + const isValidEndDate = survey.end_date ? new Date(survey.end_date) > now : true; + const isNotAnswered = !localStorage.getItem(`survey_${survey.id}_answered`); + const isNotSkipped = !localStorage.getItem(`survey_${survey.id}_skipped`); + return isValidEndDate && isNotAnswered && isNotSkipped; + }); } export function getValidStopSurvey(surveys, stop) { - return ( - surveys.find( - (survey) => - survey.show_on_stops && - ((survey.visible_stop_list && survey.visible_stop_list.includes(stop.id)) || - (survey.visible_route_list !== null && - survey.visible_route_list.some((routeId) => stop.routeIds.includes(routeId)))) - ) || null - ); + for (const survey of surveys) { + if (!survey.show_on_stops) continue; + + if (survey.visible_stop_list && survey.visible_stop_list.includes(stop.id)) { + return survey; + } + + if ( + survey.visible_route_list !== null && + Array.isArray(survey.visible_route_list) && + stop.routeIds && + Array.isArray(stop.routeIds) + ) { + for (const routeId of survey.visible_route_list) { + if (stop.routeIds.includes(routeId)) { + return survey; + } + } + } + } + return null; } + export function getShowSurveyOnAllStops(surveys) { return ( surveys.find((survey) => survey.show_on_stops && survey.visible_stop_list === null) || null @@ -58,6 +71,7 @@ export function getMapSurvey(surveys) { } export async function submitHeroQuestion(surveyResponse) { + try { const payload = { ...surveyResponse, @@ -119,3 +133,4 @@ export function skipSurvey(survey) { localStorage.setItem(`survey_${survey.id}_skipped`, true); showSurveyModal.set(false); } + From 0e3865a4ede585249942c25cda8ac4d9eb543d87 Mon Sep 17 00:00:00 2001 From: Ahmedhossamdev Date: Sat, 8 Feb 2025 07:27:58 +0200 Subject: [PATCH 22/24] Refactor StopPane to use HeroQuestion component --- src/components/stops/StopPane.svelte | 74 ++++++++-------------- src/components/surveys/HeroQuestion.svelte | 31 +++++++++ 2 files changed, 56 insertions(+), 49 deletions(-) create mode 100644 src/components/surveys/HeroQuestion.svelte diff --git a/src/components/stops/StopPane.svelte b/src/components/stops/StopPane.svelte index 47fa2a5..c142976 100644 --- a/src/components/stops/StopPane.svelte +++ b/src/components/stops/StopPane.svelte @@ -12,6 +12,7 @@ import { submitHeroQuestion, skipSurvey } from '$lib/Surveys/surveyUtils'; import { surveyStore, showSurveyModal } from '$stores/surveyStore'; import { getUserId } from '$lib/utils/user'; + import HeroQuestion from '$components/surveys/HeroQuestion.svelte'; /** * @typedef {Object} Props @@ -92,29 +93,30 @@ let showHeroQuestion = $state(true); async function handleNext() { - if (heroAnswer && heroAnswer.trim() != '') { - showSurveyModal.set(true); - nextSurveyQuestion = true; - - let surveyResponse = { - survey_id: currentStopSurvey.id, - user_identifier: getUserId(), - stop_identifier: stop.id, - stop_latitude: stop.lat, - stop_longitude: stop.lon, - responses: [] - }; - - surveyResponse.responses[0] = { - question_id: currentStopSurvey.questions[0].id, - question_label: currentStopSurvey.questions[0].content.label_text, - question_type: currentStopSurvey.questions[0].content.type, - answer: heroAnswer - }; - - surveyPublicIdentifier = await submitHeroQuestion(surveyResponse); - showHeroQuestion = false; + if (!heroAnswer || heroAnswer.trim() === '') { + return; } + showSurveyModal.set(true); + nextSurveyQuestion = true; + + let surveyResponse = { + survey_id: currentStopSurvey.id, + user_identifier: getUserId(), + stop_identifier: stop.id, + stop_latitude: stop.lat, + stop_longitude: stop.lon, + responses: [] + }; + + surveyResponse.responses[0] = { + question_id: currentStopSurvey.questions[0].id, + question_label: currentStopSurvey.questions[0].content.label_text, + question_type: currentStopSurvey.questions[0].content.type, + answer: heroAnswer + }; + + surveyPublicIdentifier = await submitHeroQuestion(surveyResponse); + showHeroQuestion = false; } function handleSkip() { @@ -162,34 +164,8 @@ {#if showHeroQuestion && currentStopSurvey} -
- -

{currentStopSurvey.name}

- -
- -
-
+ {/if} - {#if nextSurveyQuestion} + import SurveyQuestion from '$components/surveys/SurveyQuestion.svelte'; + let { currentStopSurvey, handleSkip, handleNext, handleHeroQuestionChange } = $props(); + + +
+ +

{currentStopSurvey.name}

+ +
+ +
+
From dc1729dcacf290ed1b7961197f0ce165296cfe25 Mon Sep 17 00:00:00 2001 From: Ahmedhossamdev Date: Sat, 8 Feb 2025 07:28:52 +0200 Subject: [PATCH 23/24] chore: linting and formatting --- src/components/stops/StopPane.svelte | 1 - src/lib/Surveys/surveyUtils.js | 59 ++-- src/tests/lib/surveysUtils.test.js | 394 ++++++++++++++------------- 3 files changed, 229 insertions(+), 225 deletions(-) diff --git a/src/components/stops/StopPane.svelte b/src/components/stops/StopPane.svelte index c142976..2580a74 100644 --- a/src/components/stops/StopPane.svelte +++ b/src/components/stops/StopPane.svelte @@ -5,7 +5,6 @@ import Accordion from '$components/containers/SingleSelectAccordion.svelte'; import AccordionItem from '$components/containers/AccordionItem.svelte'; import SurveyModal from '$components/surveys/SurveyModal.svelte'; - import SurveyQuestion from '$components/surveys/SurveyQuestion.svelte'; import { onDestroy } from 'svelte'; import '$lib/i18n.js'; import { isLoading, t } from 'svelte-i18n'; diff --git a/src/lib/Surveys/surveyUtils.js b/src/lib/Surveys/surveyUtils.js index 8e8ab7c..13789cd 100644 --- a/src/lib/Surveys/surveyUtils.js +++ b/src/lib/Surveys/surveyUtils.js @@ -26,39 +26,38 @@ export async function loadSurveys(stop = null, userId = null) { } export function getValidSurveys(surveys) { - const now = new Date(); - return surveys.filter((survey) => { - const isValidEndDate = survey.end_date ? new Date(survey.end_date) > now : true; - const isNotAnswered = !localStorage.getItem(`survey_${survey.id}_answered`); - const isNotSkipped = !localStorage.getItem(`survey_${survey.id}_skipped`); - return isValidEndDate && isNotAnswered && isNotSkipped; - }); + const now = new Date(); + return surveys.filter((survey) => { + const isValidEndDate = survey.end_date ? new Date(survey.end_date) > now : true; + const isNotAnswered = !localStorage.getItem(`survey_${survey.id}_answered`); + const isNotSkipped = !localStorage.getItem(`survey_${survey.id}_skipped`); + return isValidEndDate && isNotAnswered && isNotSkipped; + }); } export function getValidStopSurvey(surveys, stop) { - for (const survey of surveys) { - if (!survey.show_on_stops) continue; - - if (survey.visible_stop_list && survey.visible_stop_list.includes(stop.id)) { - return survey; - } - - if ( - survey.visible_route_list !== null && - Array.isArray(survey.visible_route_list) && - stop.routeIds && - Array.isArray(stop.routeIds) - ) { - for (const routeId of survey.visible_route_list) { - if (stop.routeIds.includes(routeId)) { - return survey; - } - } - } - } - return null; -} + for (const survey of surveys) { + if (!survey.show_on_stops) continue; + + if (survey.visible_stop_list && survey.visible_stop_list.includes(stop.id)) { + return survey; + } + if ( + survey.visible_route_list !== null && + Array.isArray(survey.visible_route_list) && + stop.routeIds && + Array.isArray(stop.routeIds) + ) { + for (const routeId of survey.visible_route_list) { + if (stop.routeIds.includes(routeId)) { + return survey; + } + } + } + } + return null; +} export function getShowSurveyOnAllStops(surveys) { return ( @@ -71,7 +70,6 @@ export function getMapSurvey(surveys) { } export async function submitHeroQuestion(surveyResponse) { - try { const payload = { ...surveyResponse, @@ -133,4 +131,3 @@ export function skipSurvey(survey) { localStorage.setItem(`survey_${survey.id}_skipped`, true); showSurveyModal.set(false); } - diff --git a/src/tests/lib/surveysUtils.test.js b/src/tests/lib/surveysUtils.test.js index 48220b9..195394c 100644 --- a/src/tests/lib/surveysUtils.test.js +++ b/src/tests/lib/surveysUtils.test.js @@ -1,215 +1,223 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; -import { getValidSurveys, getValidStopSurvey, getShowSurveyOnAllStops, getMapSurvey, submitHeroQuestion, updateSurveyResponse } from '../../lib/Surveys/surveyUtils'; +import { + getValidSurveys, + getValidStopSurvey, + getShowSurveyOnAllStops, + getMapSurvey, + submitHeroQuestion, + updateSurveyResponse +} from '../../lib/Surveys/surveyUtils'; beforeEach(() => { - let store = {}; - vi.stubGlobal("localStorage", { - getItem: vi.fn((key) => store[key] || null), - setItem: vi.fn((key, value) => { - store[key] = value; - }), - removeItem: vi.fn((key) => { - delete store[key]; - }), - clear: vi.fn(() => { - store = {}; - }), - }); - - localStorage.clear(); + let store = {}; + vi.stubGlobal('localStorage', { + getItem: vi.fn((key) => store[key] || null), + setItem: vi.fn((key, value) => { + store[key] = value; + }), + removeItem: vi.fn((key) => { + delete store[key]; + }), + clear: vi.fn(() => { + store = {}; + }) + }); + + localStorage.clear(); }); - describe('getValidSurveys', () => { - it('should return surveys with valid end_date or no end_date', () => { - const now = new Date(); - const futureDate = new Date(now.getTime() + 10000).toISOString(); - const pastDate = new Date(now.getTime() - 10000).toISOString(); - - const surveys = [ - { id: 1, end_date: futureDate }, - { id: 2, end_date: pastDate }, - { id: 3 } // case when end_date is not present, the survey should be valid also. - ]; - - const valid = getValidSurveys(surveys); - expect(valid).toEqual([ - { id: 1, end_date: futureDate }, - { id: 3 } - ]); - }); - - it('should filter out surveys that were answered or skipped', () => { - const now = new Date(); - const futureDate = new Date(now.getTime() + 10000).toISOString(); - - const surveys = [ - { id: 1, end_date: futureDate }, - { id: 2, end_date: futureDate } - ]; - - localStorage.setItem('survey_1_answered', 'true'); - localStorage.setItem('survey_2_skipped', 'true'); - - const valid = getValidSurveys(surveys); - expect(valid).toEqual([]); - }); + it('should return surveys with valid end_date or no end_date', () => { + const now = new Date(); + const futureDate = new Date(now.getTime() + 10000).toISOString(); + const pastDate = new Date(now.getTime() - 10000).toISOString(); + + const surveys = [ + { id: 1, end_date: futureDate }, + { id: 2, end_date: pastDate }, + { id: 3 } // case when end_date is not present, the survey should be valid also. + ]; + + const valid = getValidSurveys(surveys); + expect(valid).toEqual([{ id: 1, end_date: futureDate }, { id: 3 }]); + }); + + it('should filter out surveys that were answered or skipped', () => { + const now = new Date(); + const futureDate = new Date(now.getTime() + 10000).toISOString(); + + const surveys = [ + { id: 1, end_date: futureDate }, + { id: 2, end_date: futureDate } + ]; + + localStorage.setItem('survey_1_answered', 'true'); + localStorage.setItem('survey_2_skipped', 'true'); + + const valid = getValidSurveys(surveys); + expect(valid).toEqual([]); + }); }); describe('getValidStopSurvey', () => { - it('should return a survey based on visible_stop_list matching stop id', () => { - const surveys = [ - { id: 1, show_on_stops: false }, - { id: 2, show_on_stops: true, visible_stop_list: ['stop1'], visible_route_list: null }, - { id: 3, show_on_stops: true, visible_stop_list: ['stop2'], visible_route_list: null } - ]; - const stop = { id: 'stop1', routeIds: [] }; - - const result = getValidStopSurvey(surveys, stop); - expect(result).toEqual(surveys[1]); - }); - - it('should return a survey based on visible_route_list matching one of stop.routeIds', () => { - const surveys = [ - { id: 1, show_on_stops: true, visible_stop_list: null, visible_route_list: ['r1', 'r2'] }, - { id: 2, show_on_stops: true, visible_stop_list: null, visible_route_list: ['r3'] } - ]; - const stop = { id: 'stop10', routeIds: ['r2'] }; - - const result = getValidStopSurvey(surveys, stop); - expect(result).toEqual(surveys[0]); - }); - - it('should return null if no survey matches', () => { - const surveys = [ - { id: 1, show_on_stops: true, visible_stop_list: ['stop3'], visible_route_list: ['r5'] } - ]; - const stop = { id: 'stop1', routeIds: ['r2'] }; - - const result = getValidStopSurvey(surveys, stop); - expect(result).toBeNull(); - }); + it('should return a survey based on visible_stop_list matching stop id', () => { + const surveys = [ + { id: 1, show_on_stops: false }, + { id: 2, show_on_stops: true, visible_stop_list: ['stop1'], visible_route_list: null }, + { id: 3, show_on_stops: true, visible_stop_list: ['stop2'], visible_route_list: null } + ]; + const stop = { id: 'stop1', routeIds: [] }; + + const result = getValidStopSurvey(surveys, stop); + expect(result).toEqual(surveys[1]); + }); + + it('should return a survey based on visible_route_list matching one of stop.routeIds', () => { + const surveys = [ + { id: 1, show_on_stops: true, visible_stop_list: null, visible_route_list: ['r1', 'r2'] }, + { id: 2, show_on_stops: true, visible_stop_list: null, visible_route_list: ['r3'] } + ]; + const stop = { id: 'stop10', routeIds: ['r2'] }; + + const result = getValidStopSurvey(surveys, stop); + expect(result).toEqual(surveys[0]); + }); + + it('should return null if no survey matches', () => { + const surveys = [ + { id: 1, show_on_stops: true, visible_stop_list: ['stop3'], visible_route_list: ['r5'] } + ]; + const stop = { id: 'stop1', routeIds: ['r2'] }; + + const result = getValidStopSurvey(surveys, stop); + expect(result).toBeNull(); + }); }); describe('getShowSurveyOnAllStops', () => { - it('should return survey if show_on_stops is true and visible_stop_list is null', () => { - const surveys = [ - { id: 1, show_on_stops: true, visible_stop_list: null }, - { id: 2, show_on_stops: true, visible_stop_list: ['stop1'] } - ]; - - const result = getShowSurveyOnAllStops(surveys); - expect(result).toEqual(surveys[0]); - }); - - it('should return null if no survey meets the criteria', () => { - const surveys = [ - { id: 1, show_on_stops: false, visible_stop_list: null }, - { id: 2, show_on_stops: true, visible_stop_list: ['stop1'] } - ]; - - const result = getShowSurveyOnAllStops(surveys); - expect(result).toBeNull(); - }); + it('should return survey if show_on_stops is true and visible_stop_list is null', () => { + const surveys = [ + { id: 1, show_on_stops: true, visible_stop_list: null }, + { id: 2, show_on_stops: true, visible_stop_list: ['stop1'] } + ]; + + const result = getShowSurveyOnAllStops(surveys); + expect(result).toEqual(surveys[0]); + }); + + it('should return null if no survey meets the criteria', () => { + const surveys = [ + { id: 1, show_on_stops: false, visible_stop_list: null }, + { id: 2, show_on_stops: true, visible_stop_list: ['stop1'] } + ]; + + const result = getShowSurveyOnAllStops(surveys); + expect(result).toBeNull(); + }); }); describe('getMapSurvey', () => { - it('should return the survey with show_on_map true', () => { - const surveys = [ - { id: 1, show_on_map: false }, - { id: 2, show_on_map: true } - ]; - - const result = getMapSurvey(surveys); - expect(result).toEqual(surveys[1]); - }); - - it('should return null if no survey has show_on_map true', () => { - const surveys = [ - { id: 1, show_on_map: false }, - { id: 2, show_on_map: false } - ]; - - const result = getMapSurvey(surveys); - expect(result).toBeNull(); - }); + it('should return the survey with show_on_map true', () => { + const surveys = [ + { id: 1, show_on_map: false }, + { id: 2, show_on_map: true } + ]; + + const result = getMapSurvey(surveys); + expect(result).toEqual(surveys[1]); + }); + + it('should return null if no survey has show_on_map true', () => { + const surveys = [ + { id: 1, show_on_map: false }, + { id: 2, show_on_map: false } + ]; + + const result = getMapSurvey(surveys); + expect(result).toBeNull(); + }); }); describe('submitHeroQuestion', () => { - beforeEach(() => { - global.fetch = vi.fn(); - }); - - afterEach(() => { - vi.resetAllMocks(); - }); - - it('should resolve with data when response is ok', async () => { -const mockResponseData = { survey_response: { id: "1" } }; - global.fetch.mockResolvedValue({ - ok: true, - json: async () => mockResponseData, - }); - - const surveyResponse = { id: 1, user_identifier: 'user123', stop_identifier: 'stop456', - "responses": "[{ question_id: 1, question_label: 'Question', question_type: 'radio', answer: 'yes' }]" }; - const result = await submitHeroQuestion(surveyResponse); - - expect(result).toEqual(mockResponseData.survey_response.id); - expect(global.fetch).toHaveBeenCalled(); - }); - - it('should throw an error when response is not ok', async () => { - global.fetch.mockResolvedValue({ - ok: false, - }); - -await expect(submitHeroQuestion({})).rejects.toThrow(expect.any(Error)); - }); + beforeEach(() => { + global.fetch = vi.fn(); + }); + + afterEach(() => { + vi.resetAllMocks(); + }); + + it('should resolve with data when response is ok', async () => { + const mockResponseData = { survey_response: { id: '1' } }; + global.fetch.mockResolvedValue({ + ok: true, + json: async () => mockResponseData + }); + + const surveyResponse = { + id: 1, + user_identifier: 'user123', + stop_identifier: 'stop456', + responses: + "[{ question_id: 1, question_label: 'Question', question_type: 'radio', answer: 'yes' }]" + }; + const result = await submitHeroQuestion(surveyResponse); + + expect(result).toEqual(mockResponseData.survey_response.id); + expect(global.fetch).toHaveBeenCalled(); + }); + + it('should throw an error when response is not ok', async () => { + global.fetch.mockResolvedValue({ + ok: false + }); + + await expect(submitHeroQuestion({})).rejects.toThrow(expect.any(Error)); + }); }); - describe('updateSurveyResponse', () => { - beforeEach(() => { - global.fetch = vi.fn(); - }); - - afterEach(() => { - vi.resetAllMocks(); - }); - - it('should resolve with true when response is ok', async () => { - global.fetch.mockResolvedValue({ - ok: true, - }); - - const surveyPublicIdentifier = "abc123"; - const surveyResponse = { - id: 1, - user_identifier: 'user123', - stop_identifier: 'stop456', - responses: [{ question_id: 1, answer: 'updated answer' }] - }; - - const result = await updateSurveyResponse(surveyPublicIdentifier, surveyResponse); - - expect(result).toEqual(true); - }); - - it('should throw an error when response is not ok', async () => { - global.fetch.mockResolvedValue({ - ok: false, - }); - - const surveyPublicIdentifier = "abc123"; - const surveyResponse = { - id: 1, - user_identifier: 'user123', - stop_identifier: 'stop456', - responses: [{ question_id: 1, answer: 'updated answer' }] - }; - - await expect(updateSurveyResponse(surveyPublicIdentifier, surveyResponse)) - .rejects.toThrow('Failed to update survey response'); - }); + beforeEach(() => { + global.fetch = vi.fn(); + }); + + afterEach(() => { + vi.resetAllMocks(); + }); + + it('should resolve with true when response is ok', async () => { + global.fetch.mockResolvedValue({ + ok: true + }); + + const surveyPublicIdentifier = 'abc123'; + const surveyResponse = { + id: 1, + user_identifier: 'user123', + stop_identifier: 'stop456', + responses: [{ question_id: 1, answer: 'updated answer' }] + }; + + const result = await updateSurveyResponse(surveyPublicIdentifier, surveyResponse); + + expect(result).toEqual(true); + }); + + it('should throw an error when response is not ok', async () => { + global.fetch.mockResolvedValue({ + ok: false + }); + + const surveyPublicIdentifier = 'abc123'; + const surveyResponse = { + id: 1, + user_identifier: 'user123', + stop_identifier: 'stop456', + responses: [{ question_id: 1, answer: 'updated answer' }] + }; + + await expect(updateSurveyResponse(surveyPublicIdentifier, surveyResponse)).rejects.toThrow( + 'Failed to update survey response' + ); + }); }); From 20cbb7606d1f35d1f2ea79fda473a8b01d9020fe Mon Sep 17 00:00:00 2001 From: Ahmedhossamdev Date: Mon, 10 Feb 2025 03:43:45 +0200 Subject: [PATCH 24/24] Refactor StopPane and SurveyQuestion components to handle label question types correctly --- src/components/stops/StopPane.svelte | 10 ++++++---- src/components/surveys/SurveyQuestion.svelte | 18 +++++++++++++----- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/components/stops/StopPane.svelte b/src/components/stops/StopPane.svelte index 2580a74..095bb49 100644 --- a/src/components/stops/StopPane.svelte +++ b/src/components/stops/StopPane.svelte @@ -92,7 +92,9 @@ let showHeroQuestion = $state(true); async function handleNext() { - if (!heroAnswer || heroAnswer.trim() === '') { + let heroQuestion = currentStopSurvey.questions[0]; + + if (heroQuestion.content.type !== 'label' && (!heroAnswer || heroAnswer.trim() === '')) { return; } showSurveyModal.set(true); @@ -108,9 +110,9 @@ }; surveyResponse.responses[0] = { - question_id: currentStopSurvey.questions[0].id, - question_label: currentStopSurvey.questions[0].content.label_text, - question_type: currentStopSurvey.questions[0].content.type, + question_id: heroQuestion.id, + question_label: heroQuestion.content.label_text, + question_type: heroQuestion.content.type, answer: heroAnswer }; diff --git a/src/components/surveys/SurveyQuestion.svelte b/src/components/surveys/SurveyQuestion.svelte index 75fedc6..180e888 100644 --- a/src/components/surveys/SurveyQuestion.svelte +++ b/src/components/surveys/SurveyQuestion.svelte @@ -35,8 +35,10 @@ @@ -78,6 +80,10 @@ {/each} +{:else if question.content.type === 'label'} +

+ {question.content.label_text} +

{:else if question.content.type === 'external_survey'} {/if} -{#if error} -

This question is required.

+{#if error && question.content.type !== 'label'} +

This question is required.

{/if}