Skip to content

Commit c45f8fc

Browse files
Merge pull request #198 from OneBusAway/refactor/survey-hero-question
Refactor/survey-hero-question
2 parents 4220475 + 4422c49 commit c45f8fc

File tree

9 files changed

+138
-44
lines changed

9 files changed

+138
-44
lines changed

src/components/search/SearchPane.svelte

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,16 @@
1111
import { env } from '$env/dynamic/public';
1212
import TripPlan from '$components/trip-planner/TripPlan.svelte';
1313
import { isMapLoaded } from '$src/stores/mapStore';
14+
import { answeredSurveys, surveyStore } from '$stores/surveyStore';
1415
1516
let {
1617
clearPolylines,
1718
handleRouteSelected,
1819
handleViewAllRoutes,
1920
handleTripPlan,
2021
cssClasses = '',
21-
mapProvider = null
22+
mapProvider = null,
23+
childContent
2224
} = $props();
2325
2426
let routes = $state(null);
@@ -28,6 +30,7 @@
2830
let polylines = [];
2931
let currentIntervalId = null;
3032
let mapLoaded = $state(false);
33+
let isSurveyAnswered = $state(false);
3134
3235
function handleLocationClick(location) {
3336
clearResults();
@@ -110,6 +113,14 @@
110113
window.dispatchEvent(event);
111114
}
112115
116+
$effect(() => {
117+
if ($surveyStore && $surveyStore.id) {
118+
isSurveyAnswered = $answeredSurveys[$surveyStore.id] === true;
119+
} else {
120+
isSurveyAnswered = false;
121+
}
122+
});
123+
113124
onMount(() => {
114125
isMapLoaded.subscribe((value) => {
115126
mapLoaded = value;
@@ -126,6 +137,12 @@
126137
<TabItem open title={$t('tabs.stops-and-stations')} on:click={handleTabSwitch}>
127138
<SearchField value={query} {handleSearchResults} />
128139

140+
{#if !isSurveyAnswered && $surveyStore}
141+
<div class="mt-2">
142+
{@render childContent()}
143+
</div>
144+
{/if}
145+
129146
{#if query}
130147
<p class="text-sm text-gray-700 dark:text-gray-400">
131148
{$t('search.results_for')} "{query}".

src/components/stops/StopPane.svelte

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import '$lib/i18n.js';
1111
import { isLoading, t } from 'svelte-i18n';
1212
import { submitHeroQuestion, skipSurvey } from '$lib/Surveys/surveyUtils';
13-
import { surveyStore, showSurveyModal } from '$stores/surveyStore';
13+
import { surveyStore, showSurveyModal, markSurveyAnswered } from '$stores/surveyStore';
1414
import { getUserId } from '$lib/utils/user';
1515
import HeroQuestion from '$components/surveys/HeroQuestion.svelte';
1616
import analytics from '$lib/Analytics/PlausibleAnalytics';
@@ -37,6 +37,7 @@
3737
3838
let interval = null;
3939
let currentStopSurvey = $state(null);
40+
let remainingSurveyQuestions = $state([]);
4041
4142
async function loadData(stopID) {
4243
loading = true;
@@ -102,13 +103,17 @@
102103
let surveyPublicIdentifier = $state(null);
103104
let showHeroQuestion = $state(true);
104105
105-
async function handleNext() {
106+
async function handleSurveyButtonClick() {
106107
let heroQuestion = currentStopSurvey.questions[0];
107-
108+
remainingSurveyQuestions = currentStopSurvey.questions.slice(1);
108109
if (heroQuestion.content.type !== 'label' && (!heroAnswer || heroAnswer.trim() === '')) {
109110
return;
110111
}
111-
showSurveyModal.set(true);
112+
113+
// If there are more questions, show the modal
114+
if (remainingSurveyQuestions.length > 0) {
115+
showSurveyModal.set(true);
116+
}
112117
nextSurveyQuestion = true;
113118
114119
let surveyResponse = {
@@ -129,6 +134,8 @@
129134
130135
surveyPublicIdentifier = await submitHeroQuestion(surveyResponse);
131136
showHeroQuestion = false;
137+
138+
markSurveyAnswered(currentStopSurvey.id);
132139
}
133140
134141
function handleSkip() {
@@ -183,7 +190,13 @@
183190
{/if}
184191
185192
{#if showHeroQuestion && currentStopSurvey}
186-
<HeroQuestion {currentStopSurvey} {handleSkip} {handleNext} {handleHeroQuestionChange} />
193+
<HeroQuestion
194+
{currentStopSurvey}
195+
{handleSkip}
196+
{handleSurveyButtonClick}
197+
{handleHeroQuestionChange}
198+
remainingQuestionsLength={remainingSurveyQuestions.length}
199+
/>
187200
{/if}
188201
{#if nextSurveyQuestion}
189202
<SurveyModal

src/components/surveys/HeroQuestion.svelte

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
<script>
22
import SurveyQuestion from '$components/surveys/SurveyQuestion.svelte';
3-
let { currentStopSurvey, handleSkip, handleNext, handleHeroQuestionChange } = $props();
3+
let {
4+
currentStopSurvey,
5+
handleSkip,
6+
handleSurveyButtonClick,
7+
handleHeroQuestionChange,
8+
remainingQuestionsLength
9+
} = $props();
410
</script>
511

612
<div class="hero-question-container relative rounded-lg bg-gray-50 p-6 shadow dark:bg-gray-800">
@@ -22,10 +28,10 @@
2228
/>
2329
<div class="mt-4 flex justify-end">
2430
<button
25-
onclick={handleNext}
26-
class="rounded bg-green-500 px-4 py-3 text-white shadow transition hover:bg-green-600 dark:bg-green-600 dark:hover:bg-green-700"
31+
onclick={handleSurveyButtonClick}
32+
class="rounded bg-green-500 px-4 py-3 text-sm text-white shadow transition hover:bg-green-600 dark:bg-green-600 dark:hover:bg-green-700"
2733
>
28-
Next
34+
{remainingQuestionsLength === 0 ? 'Submit' : 'Next'}
2935
</button>
3036
</div>
3137
</div>
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<script>
2+
import { onDestroy, tick } from 'svelte';
3+
import { showSurveyModal, surveyStore } from '$stores/surveyStore';
4+
5+
let currentSurvey;
6+
7+
const unsubscribe = surveyStore.subscribe((value) => (currentSurvey = value));
8+
9+
async function launchSurvey() {
10+
showSurveyModal.set(false);
11+
await tick();
12+
showSurveyModal.set(true);
13+
}
14+
15+
function shouldShowSurvey(survey) {
16+
return survey?.show_on_map;
17+
}
18+
19+
onDestroy(unsubscribe);
20+
</script>
21+
22+
{#if shouldShowSurvey(currentSurvey)}
23+
<div
24+
class="rounded-x w-full border border-gray-200 bg-white p-5 shadow-sm dark:border-gray-700 dark:bg-black"
25+
>
26+
<div class="flex items-center justify-between">
27+
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">
28+
{currentSurvey.name}
29+
</h2>
30+
<span
31+
class="rounded-full bg-green-100 px-3 py-1 text-xs font-medium text-green-700 dark:bg-green-900 dark:text-green-300"
32+
>
33+
New Survey
34+
</span>
35+
</div>
36+
37+
<p class="mt-3 text-sm text-gray-600 dark:text-gray-400">
38+
{currentSurvey.questions[0].content.label_text}
39+
</p>
40+
41+
<button
42+
onclick={launchSurvey}
43+
class="mt-4 flex w-full items-center justify-center rounded-md bg-green-600 px-4 py-2 text-sm font-medium text-white transition duration-200 hover:bg-green-700 focus:ring-2 focus:ring-green-400 focus:ring-opacity-50"
44+
>
45+
Take Survey
46+
</button>
47+
</div>
48+
{/if}

src/components/surveys/SurveyModal.svelte

Lines changed: 9 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
1111
import { showSurveyModal, surveyStore } from '$stores/surveyStore';
1212
import { getUserId } from '$lib/utils/user';
13+
import SurveySubmitted from './SurveySubmitted.svelte';
1314
1415
let { stop = $bindable(null), skipHeroQuestion, surveyPublicId } = $props();
1516
@@ -91,6 +92,10 @@
9192
surveyPublicIdentifier = await submitHeroQuestionUtil(surveyResponse);
9293
heroQuestionAnswered = true;
9394
submitSurvey(currentSurvey, false);
95+
96+
if (remainingQuestions.length === 0) {
97+
surveySubmitted = true;
98+
}
9499
} catch (error) {
95100
console.error('Error submitting hero question:', error);
96101
}
@@ -119,26 +124,7 @@
119124
>
120125
<div class="flex flex-col space-y-2">
121126
{#if surveySubmitted}
122-
<div class="flex flex-1 flex-col items-center justify-center p-12">
123-
<svg
124-
xmlns="http://www.w3.org/2000/svg"
125-
class="h-20 w-20 text-green-500"
126-
fill="none"
127-
viewBox="0 0 24 24"
128-
stroke="currentColor"
129-
>
130-
<path
131-
stroke-linecap="round"
132-
stroke-linejoin="round"
133-
stroke-width="2"
134-
d="M5 13l4 4L19 7"
135-
/>
136-
</svg>
137-
<h2 class="mt-6 text-4xl font-bold text-gray-900 dark:text-white">Survey Submitted</h2>
138-
<p class="mt-3 text-xl text-gray-700 dark:text-gray-300">
139-
Thank you for taking the survey!
140-
</p>
141-
</div>
127+
<SurveySubmitted />
142128
{:else}
143129
<div class="max-h-[60vh] overflow-y-auto p-2">
144130
{#if !heroQuestionAnswered && !skipHeroQuestion}
@@ -175,16 +161,16 @@
175161
color="red"
176162
class="rounded-lg px-10 py-3 shadow-md transition-shadow hover:shadow-lg"
177163
>
178-
Skip
164+
Cancel
179165
</Button>
180166
<Button
181167
onclick={submitHeroQuestion}
182168
color="green"
183169
class="rounded-lg px-10 py-3 shadow-md transition-shadow hover:shadow-lg"
184170
>
185-
Next
171+
{remainingQuestions.length === 0 ? 'Submit' : 'Next'}
186172
</Button>
187-
{:else}
173+
{:else if remainingQuestions.length > 0}
188174
<Button
189175
onclick={() => skipSurvey(currentSurvey)}
190176
color="red"
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<script>
2+
let { message = 'Thank you for taking the survey!' } = $props();
3+
</script>
4+
5+
<div class="flex flex-1 flex-col items-center justify-center p-12">
6+
<svg
7+
xmlns="http://www.w3.org/2000/svg"
8+
class="h-20 w-20 text-green-500"
9+
fill="none"
10+
viewBox="0 0 24 24"
11+
stroke="currentColor"
12+
>
13+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
14+
</svg>
15+
<h2 class="mt-6 text-4xl font-bold text-gray-900 dark:text-white">Survey Submitted</h2>
16+
<p class="mt-3 text-xl text-gray-700 dark:text-gray-300">
17+
{message}
18+
</p>
19+
</div>

src/lib/Surveys/surveyUtils.js

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { showSurveyModal, surveyStore } from '$stores/surveyStore.js';
1+
import { markSurveyAnswered, showSurveyModal, surveyStore } from '$stores/surveyStore.js';
22

33
export async function loadSurveys(stop = null, userId = null) {
44
try {
@@ -19,10 +19,7 @@ export async function loadSurveys(stop = null, userId = null) {
1919

2020
// This is the case when there's multiple surveys and we need to prioritize the one-time survey over the always visible one
2121
selectedSurvey = getPrioritySurvey(validSurveys, selectedSurvey);
22-
2322
surveyStore.set(selectedSurvey);
24-
25-
showSurveyModal.set(shouldShowSurvey(selectedSurvey));
2623
} catch (error) {
2724
console.error('Error loading surveys:', error);
2825
}
@@ -73,11 +70,7 @@ export function getValidSurveys(surveys) {
7370
export function shouldShowSurvey(survey) {
7471
if (!survey) return false;
7572

76-
if (!survey.always_visible) {
77-
return true;
78-
}
79-
80-
if (survey.allows_multiple_responses) {
73+
if ((survey.always_visible && survey.allows_multiple_responses) || survey.show_on_map) {
8174
return true;
8275
}
8376

@@ -169,7 +162,7 @@ export async function updateSurveyResponse(surveyPublicIdentifier, surveyRespons
169162
}
170163

171164
export function submitSurvey(survey, hideSurveyModal) {
172-
localStorage.setItem(`survey_${survey.id}_answered`, true);
165+
markSurveyAnswered(survey.id);
173166
if (hideSurveyModal) {
174167
setTimeout(() => {
175168
showSurveyModal.set(false);

src/routes/+page.svelte

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import analytics from '$lib/Analytics/PlausibleAnalytics';
1919
import { userLocation } from '$src/stores/userLocationStore';
2020
import { analyticsDistanceToStop } from '$lib/Analytics/plausibleUtils';
21+
import SurveyLauncher from '$components/surveys/SurveyLauncher.svelte';
2122
2223
let stop = $state();
2324
let selectedTrip = $state(null);
@@ -227,7 +228,7 @@
227228
{handleTripPlan}
228229
>
229230
{#snippet childContent()}
230-
<!-- optional child content -->
231+
<SurveyLauncher />
231232
{/snippet}
232233
</SearchPane>
233234

src/stores/surveyStore.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,14 @@ import { writable } from 'svelte/store';
22

33
export const showSurveyModal = writable(false);
44
export const surveyStore = writable(null);
5+
6+
const initial = {};
7+
8+
export const answeredSurveys = writable(initial);
9+
10+
export function markSurveyAnswered(surveyId) {
11+
answeredSurveys.update((map) => {
12+
localStorage.setItem(`survey_${surveyId}_answered`, 'true');
13+
return { ...map, [surveyId]: true };
14+
});
15+
}

0 commit comments

Comments
 (0)