Skip to content

Commit 916b5c4

Browse files
committed
Add additional questions and other improvements to the feedback flow
1 parent 9e9f958 commit 916b5c4

7 files changed

Lines changed: 318 additions & 38 deletions

src/components/GlobalContributeModal.vue

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<script setup lang="ts">
2+
import { computed, ref, watch } from 'vue'
23
import type { ContributeForm, ContributionType } from '../composables/useGlobalContribute'
34
import PersonalDetailsFields from './PersonalDetailsFields.vue'
45
@@ -16,9 +17,24 @@ interface Emits {
1617
submit: []
1718
}
1819
19-
defineProps<Props>()
20+
const props = defineProps<Props>()
2021
const emit = defineEmits<Emits>()
2122
23+
const submitted = ref(false)
24+
25+
watch(
26+
() => props.modelValue,
27+
(open) => {
28+
if (open) submitted.value = false
29+
},
30+
)
31+
32+
const contributionTypesError = computed(() =>
33+
submitted.value && props.contributeForm.contributionTypes.length === 0
34+
? 'Please select at least one option.'
35+
: undefined,
36+
)
37+
2238
const toggleContributionType = (type: ContributionType, form: ContributeForm) => {
2339
const index = form.contributionTypes.indexOf(type)
2440
const newTypes = [...form.contributionTypes]
@@ -59,6 +75,9 @@ const toggleContributionType = (type: ContributionType, form: ContributeForm) =>
5975
hide-details
6076
></v-checkbox>
6177
</div>
78+
<p v-if="contributionTypesError" class="text-error text-caption mt-2 mb-0">
79+
{{ contributionTypesError }}
80+
</p>
6281
</div>
6382

6483
<v-textarea
@@ -89,6 +108,7 @@ const toggleContributionType = (type: ContributionType, form: ContributeForm) =>
89108
:email="contributeForm.email"
90109
:organization="contributeForm.organization"
91110
required
111+
:submitted="submitted"
92112
density="compact"
93113
field-spacing="mb-2"
94114
@update:name="emit('update:form', 'name', $event)"
@@ -103,9 +123,8 @@ const toggleContributionType = (type: ContributionType, form: ContributeForm) =>
103123
<v-btn
104124
color="teal"
105125
variant="flat"
106-
:disabled="!canSubmit"
107126
:loading="isSubmitting"
108-
@click="emit('submit')"
127+
@click="submitted = true; emit('submit')"
109128
>
110129
Submit
111130
</v-btn>

src/components/GlobalFeedbackDetailsModal.vue

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<script setup lang="ts">
2+
import { computed, ref, watch } from 'vue'
23
import type { DetailedFeedbackForm } from '../composables/useGlobalFeedback'
4+
import { isValidEmail } from '../functions/feedback-utils'
35
import PersonalDetailsFields from './PersonalDetailsFields.vue'
46
57
interface Props {
@@ -15,8 +17,32 @@ interface Emits {
1517
submit: []
1618
}
1719
18-
defineProps<Props>()
20+
const props = defineProps<Props>()
1921
const emit = defineEmits<Emits>()
22+
23+
const submitted = ref(false)
24+
25+
watch(
26+
() => props.modelValue,
27+
(open) => {
28+
if (open) submitted.value = false
29+
},
30+
)
31+
32+
const qualityFeedbackError = computed(() =>
33+
submitted.value && !props.detailsForm.qualityFeedback.trim() ? 'This field is required.' : undefined,
34+
)
35+
36+
const useCaseError = computed(() =>
37+
submitted.value && !props.detailsForm.useCase.trim() ? 'This field is required.' : undefined,
38+
)
39+
40+
const emailError = computed(() => {
41+
if (props.detailsForm.email && !isValidEmail(props.detailsForm.email)) {
42+
return 'Please enter a valid email address.'
43+
}
44+
return undefined
45+
})
2046
</script>
2147

2248
<template>
@@ -35,31 +61,32 @@ const emit = defineEmits<Emits>()
3561
<v-textarea
3662
:model-value="detailsForm.qualityFeedback"
3763
@update:model-value="emit('update:form', 'qualityFeedback', $event)"
38-
label="How can we improve field boundaries?"
64+
label="How can we improve field boundaries? *"
3965
hint="Share what's good/bad and what would make them more useful to you"
4066
variant="outlined"
4167
rows="4"
4268
auto-grow
43-
required
4469
class="mb-3"
70+
:error-messages="qualityFeedbackError"
4571
></v-textarea>
4672

4773
<v-textarea
4874
:model-value="detailsForm.useCase"
4975
@update:model-value="emit('update:form', 'useCase', $event)"
50-
label="How would you like to use field boundaries?"
76+
label="How would you like to use field boundaries? *"
5177
hint="Describe your specific use case and how field boundaries could be more useful to you"
5278
variant="outlined"
5379
rows="3"
5480
auto-grow
55-
required
5681
class="mb-3"
82+
:error-messages="useCaseError"
5783
></v-textarea>
5884

5985
<PersonalDetailsFields
6086
:name="detailsForm.name"
6187
:email="detailsForm.email"
6288
:organization="detailsForm.organization"
89+
:submitted="submitted"
6390
@update:name="emit('update:form', 'name', $event)"
6491
@update:email="emit('update:form', 'email', $event)"
6592
@update:organization="emit('update:form', 'organization', $event)"
@@ -72,9 +99,8 @@ const emit = defineEmits<Emits>()
7299
<v-btn
73100
color="teal"
74101
variant="flat"
75-
:disabled="!canSubmit"
76102
:loading="isSubmitting"
77-
@click="emit('submit')"
103+
@click="submitted = true; emit('submit')"
78104
>
79105
Submit
80106
</v-btn>
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
<script setup lang="ts">
2+
import type { FeedbackRating, RatingTag, TagOption } from '../composables/useGlobalFeedback'
3+
import { RATING_TAGS } from '../composables/useGlobalFeedback'
4+
import { computed, ref, watch } from 'vue'
5+
6+
interface Props {
7+
modelValue: boolean
8+
selectedLevel: FeedbackRating | null
9+
selectedTags: RatingTag[]
10+
isSubmitting: boolean
11+
}
12+
13+
interface Emits {
14+
'update:modelValue': [value: boolean]
15+
'update:selectedTags': [tags: RatingTag[]]
16+
submit: []
17+
tellUsMore: []
18+
}
19+
20+
const props = defineProps<Props>()
21+
const emit = defineEmits<Emits>()
22+
23+
const submitted = ref(false)
24+
25+
watch(
26+
() => props.modelValue,
27+
(open) => {
28+
if (open) submitted.value = false
29+
},
30+
)
31+
32+
const availableTags = computed<TagOption[]>(() => {
33+
if (!props.selectedLevel) return []
34+
return RATING_TAGS[props.selectedLevel]
35+
})
36+
37+
const ratingLabel = computed(() => {
38+
if (props.selectedLevel === 3) return 'Good'
39+
if (props.selectedLevel === 2) return 'Acceptable'
40+
if (props.selectedLevel === 1) return 'Poor'
41+
return ''
42+
})
43+
44+
const canSubmit = computed(() => props.selectedTags.length > 0)
45+
46+
function toggleTag(tag: RatingTag) {
47+
const current = [...props.selectedTags]
48+
const idx = current.indexOf(tag)
49+
if (idx >= 0) {
50+
current.splice(idx, 1)
51+
} else {
52+
current.push(tag)
53+
}
54+
emit('update:selectedTags', current)
55+
}
56+
</script>
57+
58+
<template>
59+
<v-dialog
60+
:model-value="modelValue"
61+
@update:model-value="emit('update:modelValue', $event)"
62+
max-width="450"
63+
>
64+
<v-card>
65+
<v-card-title class="text-h5">What did you notice?</v-card-title>
66+
<v-card-subtitle class="text-body2 px-6 pb-2 text-wrap">
67+
You rated this area as <strong>{{ ratingLabel }}</strong>. What best describes what you
68+
observed?
69+
</v-card-subtitle>
70+
71+
<v-card-text>
72+
<p v-if="submitted && !canSubmit" class="text-error text-caption mb-3 mt-0">
73+
Please select at least one option.
74+
</p>
75+
<v-checkbox
76+
v-for="tag in availableTags"
77+
:key="tag.value"
78+
:model-value="selectedTags.includes(tag.value)"
79+
:label="tag.label"
80+
color="teal"
81+
density="compact"
82+
hide-details
83+
@update:model-value="toggleTag(tag.value)"
84+
/>
85+
</v-card-text>
86+
87+
<v-card-actions>
88+
<v-spacer></v-spacer>
89+
<v-btn variant="text" @click="emit('update:modelValue', false)">Cancel</v-btn>
90+
<v-btn
91+
color="teal"
92+
variant="flat"
93+
@click="submitted = true; emit('tellUsMore')"
94+
>
95+
Tell Us More
96+
</v-btn>
97+
<v-btn
98+
color="teal"
99+
variant="flat"
100+
:loading="isSubmitting"
101+
@click="submitted = true; emit('submit')"
102+
>
103+
Submit
104+
</v-btn>
105+
</v-card-actions>
106+
</v-card>
107+
</v-dialog>
108+
</template>

src/components/GlobalFeedbackWidget.vue

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { geoJsonResults } from '../composables/useMap'
55
import useSettings from '../composables/useSettings'
66
import GlobalContributeModal from './GlobalContributeModal.vue'
77
import GlobalFeedbackDetailsModal from './GlobalFeedbackDetailsModal.vue'
8+
import GlobalFeedbackTagsModal from './GlobalFeedbackTagsModal.vue'
89
import useGlobalFeedback from '../composables/useGlobalFeedback'
910
import useGlobalContribute from '../composables/useGlobalContribute'
1011
@@ -18,13 +19,17 @@ const {
1819
selectedLevel,
1920
detailsDialogOpen,
2021
detailsForm,
22+
tagsDialogOpen,
23+
selectedTags,
2124
canProvideFeedback,
22-
zoomGateMessage,
25+
isMediumZoom,
2326
canSubmitDetailed,
2427
isSubmittingQuick,
2528
isSubmittingDetails,
2629
openDetailsDialog,
30+
openDetailsDialogFromTags,
2731
submitQuickFeedback,
32+
submitRatingWithTags,
2833
submitDetailedFeedback,
2934
} = useGlobalFeedback(map)
3035
@@ -77,15 +82,6 @@ const sliderLabels = computed(() => {
7782
<v-btn variant="outlined" color="teal" size="small" @click="openContributeDialog">
7883
Contribute
7984
</v-btn>
80-
<v-btn
81-
variant="outlined"
82-
color="teal"
83-
size="small"
84-
:disabled="!selectedLevel"
85-
@click="openDetailsDialog"
86-
>
87-
Tell Us More
88-
</v-btn>
8985
<v-btn
9086
variant="flat"
9187
color="teal"
@@ -100,7 +96,17 @@ const sliderLabels = computed(() => {
10096
</template>
10197

10298
<template v-else>
103-
<div class="zoom-message">{{ zoomGateMessage }}</div>
99+
<div class="zoom-message">
100+
<template v-if="isMediumZoom">
101+
Zoom in more to see <strong>all</strong> fields and to give feedback.
102+
</template>
103+
<template v-else> Zoom in to see fields and to give feedback. </template>
104+
</div>
105+
<div class="feedback-actions feedback-actions--center mt-3">
106+
<v-btn variant="outlined" color="teal" size="small" @click="openContributeDialog">
107+
Contribute
108+
</v-btn>
109+
</div>
104110
</template>
105111
</v-card-text>
106112
</v-card>
@@ -114,6 +120,16 @@ const sliderLabels = computed(() => {
114120
@submit="submitDetailedFeedback"
115121
/>
116122

123+
<GlobalFeedbackTagsModal
124+
v-model="tagsDialogOpen"
125+
:selected-level="selectedLevel"
126+
:selected-tags="selectedTags"
127+
:is-submitting="isSubmittingQuick"
128+
@update:selected-tags="selectedTags = $event"
129+
@submit="submitRatingWithTags"
130+
@tell-us-more="openDetailsDialogFromTags"
131+
/>
132+
117133
<GlobalContributeModal
118134
v-model="contributeDialogOpen"
119135
:contribute-form="contributeForm"
@@ -184,11 +200,19 @@ const sliderLabels = computed(() => {
184200
185201
.feedback-actions {
186202
display: flex;
187-
justify-content: flex-end;
203+
justify-content: space-between;
188204
gap: 0.35rem;
189205
margin-top: 1.2rem;
190206
}
191207
208+
.feedback-actions--center {
209+
justify-content: center;
210+
}
211+
212+
.feedback-actions--center {
213+
justify-content: center;
214+
}
215+
192216
.zoom-message {
193217
font-size: 0.8rem;
194218
text-align: center;

0 commit comments

Comments
 (0)