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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion src/components/search/SearchPane.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,16 @@
import { env } from '$env/dynamic/public';
import TripPlan from '$components/trip-planner/TripPlan.svelte';
import { isMapLoaded } from '$src/stores/mapStore';
import { answeredSurveys, surveyStore } from '$stores/surveyStore';

let {
clearPolylines,
handleRouteSelected,
handleViewAllRoutes,
handleTripPlan,
cssClasses = '',
mapProvider = null
mapProvider = null,
childContent
} = $props();

let routes = $state(null);
Expand All @@ -28,6 +30,7 @@
let polylines = [];
let currentIntervalId = null;
let mapLoaded = $state(false);
let isSurveyAnswered = $state(false);

function handleLocationClick(location) {
clearResults();
Expand Down Expand Up @@ -110,6 +113,14 @@
window.dispatchEvent(event);
}

$effect(() => {
if ($surveyStore && $surveyStore.id) {
isSurveyAnswered = $answeredSurveys[$surveyStore.id] === true;
} else {
isSurveyAnswered = false;
}
});

onMount(() => {
isMapLoaded.subscribe((value) => {
mapLoaded = value;
Expand All @@ -126,6 +137,12 @@
<TabItem open title={$t('tabs.stops-and-stations')} on:click={handleTabSwitch}>
<SearchField value={query} {handleSearchResults} />

{#if !isSurveyAnswered && $surveyStore}
<div class="mt-2">
{@render childContent()}
</div>
{/if}

{#if query}
<p class="text-sm text-gray-700 dark:text-gray-400">
{$t('search.results_for')} "{query}".
Expand Down
23 changes: 18 additions & 5 deletions src/components/stops/StopPane.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import '$lib/i18n.js';
import { isLoading, t } from 'svelte-i18n';
import { submitHeroQuestion, skipSurvey } from '$lib/Surveys/surveyUtils';
import { surveyStore, showSurveyModal } from '$stores/surveyStore';
import { surveyStore, showSurveyModal, markSurveyAnswered } from '$stores/surveyStore';
import { getUserId } from '$lib/utils/user';
import HeroQuestion from '$components/surveys/HeroQuestion.svelte';
import analytics from '$lib/Analytics/PlausibleAnalytics';
Expand All @@ -37,6 +37,7 @@

let interval = null;
let currentStopSurvey = $state(null);
let remainingSurveyQuestions = $state([]);

async function loadData(stopID) {
loading = true;
Expand Down Expand Up @@ -102,13 +103,17 @@
let surveyPublicIdentifier = $state(null);
let showHeroQuestion = $state(true);

async function handleNext() {
async function handleSurveyButtonClick() {
let heroQuestion = currentStopSurvey.questions[0];

remainingSurveyQuestions = currentStopSurvey.questions.slice(1);
if (heroQuestion.content.type !== 'label' && (!heroAnswer || heroAnswer.trim() === '')) {
return;
}
showSurveyModal.set(true);

// If there are more questions, show the modal
if (remainingSurveyQuestions.length > 0) {
showSurveyModal.set(true);
}
nextSurveyQuestion = true;

let surveyResponse = {
Expand All @@ -129,6 +134,8 @@

surveyPublicIdentifier = await submitHeroQuestion(surveyResponse);
showHeroQuestion = false;

markSurveyAnswered(currentStopSurvey.id);
}

function handleSkip() {
Expand Down Expand Up @@ -183,7 +190,13 @@
{/if}

{#if showHeroQuestion && currentStopSurvey}
<HeroQuestion {currentStopSurvey} {handleSkip} {handleNext} {handleHeroQuestionChange} />
<HeroQuestion
{currentStopSurvey}
{handleSkip}
{handleSurveyButtonClick}
{handleHeroQuestionChange}
remainingQuestionsLength={remainingSurveyQuestions.length}
/>
{/if}
{#if nextSurveyQuestion}
<SurveyModal
Expand Down
14 changes: 10 additions & 4 deletions src/components/surveys/HeroQuestion.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
<script>
import SurveyQuestion from '$components/surveys/SurveyQuestion.svelte';
let { currentStopSurvey, handleSkip, handleNext, handleHeroQuestionChange } = $props();
let {
currentStopSurvey,
handleSkip,
handleSurveyButtonClick,
handleHeroQuestionChange,
remainingQuestionsLength
} = $props();
</script>

<div class="hero-question-container relative rounded-lg bg-gray-50 p-6 shadow dark:bg-gray-800">
Expand All @@ -22,10 +28,10 @@
/>
<div class="mt-4 flex justify-end">
<button
onclick={handleNext}
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"
onclick={handleSurveyButtonClick}
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"
>
Next
{remainingQuestionsLength === 0 ? 'Submit' : 'Next'}
</button>
</div>
</div>
48 changes: 48 additions & 0 deletions src/components/surveys/SurveyLauncher.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<script>
import { onDestroy, tick } from 'svelte';
import { showSurveyModal, surveyStore } from '$stores/surveyStore';

let currentSurvey;

const unsubscribe = surveyStore.subscribe((value) => (currentSurvey = value));

async function launchSurvey() {
showSurveyModal.set(false);
await tick();
showSurveyModal.set(true);
}

function shouldShowSurvey(survey) {
return survey?.show_on_map;
}

onDestroy(unsubscribe);
</script>

{#if shouldShowSurvey(currentSurvey)}
<div
class="rounded-x w-full border border-gray-200 bg-white p-5 shadow-sm dark:border-gray-700 dark:bg-black"
>
<div class="flex items-center justify-between">
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">
{currentSurvey.name}
</h2>
<span
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"
>
New Survey
</span>
</div>

<p class="mt-3 text-sm text-gray-600 dark:text-gray-400">
{currentSurvey.questions[0].content.label_text}
</p>

<button
onclick={launchSurvey}
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"
>
Take Survey
</button>
</div>
{/if}
32 changes: 9 additions & 23 deletions src/components/surveys/SurveyModal.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import { showSurveyModal, surveyStore } from '$stores/surveyStore';
import { getUserId } from '$lib/utils/user';
import SurveySubmitted from './SurveySubmitted.svelte';

let { stop = $bindable(null), skipHeroQuestion, surveyPublicId } = $props();

Expand Down Expand Up @@ -91,6 +92,10 @@
surveyPublicIdentifier = await submitHeroQuestionUtil(surveyResponse);
heroQuestionAnswered = true;
submitSurvey(currentSurvey, false);

if (remainingQuestions.length === 0) {
surveySubmitted = true;
}
} catch (error) {
console.error('Error submitting hero question:', error);
}
Expand Down Expand Up @@ -119,26 +124,7 @@
>
<div class="flex flex-col space-y-2">
{#if surveySubmitted}
<div class="flex flex-1 flex-col items-center justify-center p-12">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-20 w-20 text-green-500"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M5 13l4 4L19 7"
/>
</svg>
<h2 class="mt-6 text-4xl font-bold text-gray-900 dark:text-white">Survey Submitted</h2>
<p class="mt-3 text-xl text-gray-700 dark:text-gray-300">
Thank you for taking the survey!
</p>
</div>
<SurveySubmitted />
{:else}
<div class="max-h-[60vh] overflow-y-auto p-2">
{#if !heroQuestionAnswered && !skipHeroQuestion}
Expand Down Expand Up @@ -175,16 +161,16 @@
color="red"
class="rounded-lg px-10 py-3 shadow-md transition-shadow hover:shadow-lg"
>
Skip
Cancel
</Button>
<Button
onclick={submitHeroQuestion}
color="green"
class="rounded-lg px-10 py-3 shadow-md transition-shadow hover:shadow-lg"
>
Next
{remainingQuestions.length === 0 ? 'Submit' : 'Next'}
</Button>
{:else}
{:else if remainingQuestions.length > 0}
<Button
onclick={() => skipSurvey(currentSurvey)}
color="red"
Expand Down
19 changes: 19 additions & 0 deletions src/components/surveys/SurveySubmitted.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<script>
let { message = 'Thank you for taking the survey!' } = $props();
</script>

<div class="flex flex-1 flex-col items-center justify-center p-12">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-20 w-20 text-green-500"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
</svg>
<h2 class="mt-6 text-4xl font-bold text-gray-900 dark:text-white">Survey Submitted</h2>
<p class="mt-3 text-xl text-gray-700 dark:text-gray-300">
{message}
</p>
</div>
13 changes: 3 additions & 10 deletions src/lib/Surveys/surveyUtils.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { showSurveyModal, surveyStore } from '$stores/surveyStore.js';
import { markSurveyAnswered, showSurveyModal, surveyStore } from '$stores/surveyStore.js';

export async function loadSurveys(stop = null, userId = null) {
try {
Expand All @@ -19,10 +19,7 @@ export async function loadSurveys(stop = null, userId = null) {

// This is the case when there's multiple surveys and we need to prioritize the one-time survey over the always visible one
selectedSurvey = getPrioritySurvey(validSurveys, selectedSurvey);

surveyStore.set(selectedSurvey);

showSurveyModal.set(shouldShowSurvey(selectedSurvey));
} catch (error) {
console.error('Error loading surveys:', error);
}
Expand Down Expand Up @@ -73,11 +70,7 @@ export function getValidSurveys(surveys) {
export function shouldShowSurvey(survey) {
if (!survey) return false;

if (!survey.always_visible) {
return true;
}

if (survey.allows_multiple_responses) {
if ((survey.always_visible && survey.allows_multiple_responses) || survey.show_on_map) {
return true;
}

Expand Down Expand Up @@ -169,7 +162,7 @@ export async function updateSurveyResponse(surveyPublicIdentifier, surveyRespons
}

export function submitSurvey(survey, hideSurveyModal) {
localStorage.setItem(`survey_${survey.id}_answered`, true);
markSurveyAnswered(survey.id);
if (hideSurveyModal) {
setTimeout(() => {
showSurveyModal.set(false);
Expand Down
3 changes: 2 additions & 1 deletion src/routes/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import analytics from '$lib/Analytics/PlausibleAnalytics';
import { userLocation } from '$src/stores/userLocationStore';
import { analyticsDistanceToStop } from '$lib/Analytics/plausibleUtils';
import SurveyLauncher from '$components/surveys/SurveyLauncher.svelte';

let stop = $state();
let selectedTrip = $state(null);
Expand Down Expand Up @@ -227,7 +228,7 @@
{handleTripPlan}
>
{#snippet childContent()}
<!-- optional child content -->
<SurveyLauncher />
{/snippet}
</SearchPane>

Expand Down
11 changes: 11 additions & 0 deletions src/stores/surveyStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,14 @@ import { writable } from 'svelte/store';

export const showSurveyModal = writable(false);
export const surveyStore = writable(null);

const initial = {};

export const answeredSurveys = writable(initial);

export function markSurveyAnswered(surveyId) {
answeredSurveys.update((map) => {
localStorage.setItem(`survey_${surveyId}_answered`, 'true');
return { ...map, [surveyId]: true };
});
}