Skip to content

Commit 0e6e6fe

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

7 files changed

Lines changed: 348 additions & 52 deletions

src/components/GlobalContributeModal.vue

Lines changed: 27 additions & 8 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,29 @@ 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+
38+
function onSubmit() {
39+
submitted.value = true
40+
emit('submit')
41+
}
42+
2243
const toggleContributionType = (type: ContributionType, form: ContributeForm) => {
2344
const index = form.contributionTypes.indexOf(type)
2445
const newTypes = [...form.contributionTypes]
@@ -59,6 +80,9 @@ const toggleContributionType = (type: ContributionType, form: ContributeForm) =>
5980
hide-details
6081
></v-checkbox>
6182
</div>
83+
<p v-if="contributionTypesError" class="text-error text-caption mt-2 mb-0">
84+
{{ contributionTypesError }}
85+
</p>
6286
</div>
6387

6488
<v-textarea
@@ -89,6 +113,7 @@ const toggleContributionType = (type: ContributionType, form: ContributeForm) =>
89113
:email="contributeForm.email"
90114
:organization="contributeForm.organization"
91115
required
116+
:submitted="submitted"
92117
density="compact"
93118
field-spacing="mb-2"
94119
@update:name="emit('update:form', 'name', $event)"
@@ -100,13 +125,7 @@ const toggleContributionType = (type: ContributionType, form: ContributeForm) =>
100125
<v-card-actions>
101126
<v-spacer></v-spacer>
102127
<v-btn variant="text" @click="emit('update:modelValue', false)">Cancel</v-btn>
103-
<v-btn
104-
color="teal"
105-
variant="flat"
106-
:disabled="!canSubmit"
107-
:loading="isSubmitting"
108-
@click="emit('submit')"
109-
>
128+
<v-btn color="teal" variant="flat" :loading="isSubmitting" @click="onSubmit">
110129
Submit
111130
</v-btn>
112131
</v-card-actions>

src/components/GlobalFeedbackDetailsModal.vue

Lines changed: 32 additions & 12 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 { DetailedFeedbackForm } from '../composables/useGlobalFeedback'
34
import PersonalDetailsFields from './PersonalDetailsFields.vue'
45
@@ -15,8 +16,32 @@ interface Emits {
1516
submit: []
1617
}
1718
18-
defineProps<Props>()
19+
const props = defineProps<Props>()
1920
const emit = defineEmits<Emits>()
21+
22+
const submitted = ref(false)
23+
24+
watch(
25+
() => props.modelValue,
26+
(open) => {
27+
if (open) submitted.value = false
28+
},
29+
)
30+
31+
const qualityFeedbackError = computed(() =>
32+
submitted.value && !props.detailsForm.qualityFeedback.trim()
33+
? 'This field is required.'
34+
: undefined,
35+
)
36+
37+
const useCaseError = computed(() =>
38+
submitted.value && !props.detailsForm.useCase.trim() ? 'This field is required.' : undefined,
39+
)
40+
41+
function onSubmit() {
42+
submitted.value = true
43+
emit('submit')
44+
}
2045
</script>
2146

2247
<template>
@@ -35,31 +60,32 @@ const emit = defineEmits<Emits>()
3560
<v-textarea
3661
:model-value="detailsForm.qualityFeedback"
3762
@update:model-value="emit('update:form', 'qualityFeedback', $event)"
38-
label="How can we improve field boundaries?"
63+
label="How can we improve field boundaries? *"
3964
hint="Share what's good/bad and what would make them more useful to you"
4065
variant="outlined"
4166
rows="4"
4267
auto-grow
43-
required
4468
class="mb-3"
69+
:error-messages="qualityFeedbackError"
4570
></v-textarea>
4671

4772
<v-textarea
4873
:model-value="detailsForm.useCase"
4974
@update:model-value="emit('update:form', 'useCase', $event)"
50-
label="How would you like to use field boundaries?"
75+
label="How would you like to use field boundaries? *"
5176
hint="Describe your specific use case and how field boundaries could be more useful to you"
5277
variant="outlined"
5378
rows="3"
5479
auto-grow
55-
required
5680
class="mb-3"
81+
:error-messages="useCaseError"
5782
></v-textarea>
5883

5984
<PersonalDetailsFields
6085
:name="detailsForm.name"
6186
:email="detailsForm.email"
6287
:organization="detailsForm.organization"
88+
:submitted="submitted"
6389
@update:name="emit('update:form', 'name', $event)"
6490
@update:email="emit('update:form', 'email', $event)"
6591
@update:organization="emit('update:form', 'organization', $event)"
@@ -69,13 +95,7 @@ const emit = defineEmits<Emits>()
6995
<v-card-actions>
7096
<v-spacer></v-spacer>
7197
<v-btn variant="text" @click="emit('update:modelValue', false)">Cancel</v-btn>
72-
<v-btn
73-
color="teal"
74-
variant="flat"
75-
:disabled="!canSubmit"
76-
:loading="isSubmitting"
77-
@click="emit('submit')"
78-
>
98+
<v-btn color="teal" variant="flat" :loading="isSubmitting" @click="onSubmit">
7999
Submit
80100
</v-btn>
81101
</v-card-actions>
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
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+
const pendingAction = ref<'submit' | 'tellUsMore' | null>(null)
47+
48+
watch(
49+
() => props.isSubmitting,
50+
(val) => {
51+
if (!val) pendingAction.value = null
52+
},
53+
)
54+
55+
function onTellUsMore() {
56+
submitted.value = true
57+
pendingAction.value = 'tellUsMore'
58+
emit('tellUsMore')
59+
}
60+
61+
function onSubmit() {
62+
submitted.value = true
63+
pendingAction.value = 'submit'
64+
emit('submit')
65+
}
66+
67+
function toggleTag(tag: RatingTag) {
68+
const current = [...props.selectedTags]
69+
const idx = current.indexOf(tag)
70+
if (idx >= 0) {
71+
current.splice(idx, 1)
72+
} else {
73+
current.push(tag)
74+
}
75+
emit('update:selectedTags', current)
76+
}
77+
</script>
78+
79+
<template>
80+
<v-dialog
81+
:model-value="modelValue"
82+
@update:model-value="emit('update:modelValue', $event)"
83+
max-width="450"
84+
>
85+
<v-card>
86+
<v-card-title class="text-h5">What did you notice?</v-card-title>
87+
<v-card-subtitle class="text-body2 px-6 pb-2 text-wrap">
88+
You rated this area as <strong>{{ ratingLabel }}</strong
89+
>. What best describes what you observed?
90+
</v-card-subtitle>
91+
92+
<v-card-text>
93+
<p v-if="submitted && !canSubmit" class="text-error text-caption mb-3 mt-0">
94+
Please select at least one option.
95+
</p>
96+
<v-checkbox
97+
v-for="tag in availableTags"
98+
:key="tag.value"
99+
:model-value="selectedTags.includes(tag.value)"
100+
:label="tag.label"
101+
color="teal"
102+
density="compact"
103+
hide-details
104+
@update:model-value="toggleTag(tag.value)"
105+
/>
106+
</v-card-text>
107+
108+
<v-card-actions>
109+
<v-spacer></v-spacer>
110+
<v-btn variant="text" @click="emit('update:modelValue', false)">Cancel</v-btn>
111+
<v-btn
112+
color="teal"
113+
variant="flat"
114+
:loading="isSubmitting && pendingAction === 'tellUsMore'"
115+
@click="onTellUsMore"
116+
>
117+
Tell Us More
118+
</v-btn>
119+
<v-btn
120+
color="teal"
121+
variant="flat"
122+
:loading="isSubmitting && pendingAction === 'submit'"
123+
@click="onSubmit"
124+
>
125+
Submit
126+
</v-btn>
127+
</v-card-actions>
128+
</v-card>
129+
</v-dialog>
130+
</template>

src/components/GlobalFeedbackWidget.vue

Lines changed: 36 additions & 13 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,16 @@ const {
1819
selectedLevel,
1920
detailsDialogOpen,
2021
detailsForm,
22+
tagsDialogOpen,
23+
selectedTags,
2124
canProvideFeedback,
22-
zoomGateMessage,
25+
isMediumZoom,
2326
canSubmitDetailed,
2427
isSubmittingQuick,
2528
isSubmittingDetails,
26-
openDetailsDialog,
29+
openDetailsDialogFromTags,
2730
submitQuickFeedback,
31+
submitRatingWithTags,
2832
submitDetailedFeedback,
2933
} = useGlobalFeedback(map)
3034
@@ -77,15 +81,6 @@ const sliderLabels = computed(() => {
7781
<v-btn variant="outlined" color="teal" size="small" @click="openContributeDialog">
7882
Contribute
7983
</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>
8984
<v-btn
9085
variant="flat"
9186
color="teal"
@@ -100,7 +95,17 @@ const sliderLabels = computed(() => {
10095
</template>
10196

10297
<template v-else>
103-
<div class="zoom-message">{{ zoomGateMessage }}</div>
98+
<div class="zoom-message">
99+
<template v-if="isMediumZoom">
100+
Zoom in more to see <strong>all</strong> fields and to give feedback.
101+
</template>
102+
<template v-else> Zoom in to see fields and to give feedback. </template>
103+
</div>
104+
<div class="feedback-actions feedback-actions--center mt-3">
105+
<v-btn variant="outlined" color="teal" size="small" @click="openContributeDialog">
106+
Contribute
107+
</v-btn>
108+
</div>
104109
</template>
105110
</v-card-text>
106111
</v-card>
@@ -114,6 +119,16 @@ const sliderLabels = computed(() => {
114119
@submit="submitDetailedFeedback"
115120
/>
116121

122+
<GlobalFeedbackTagsModal
123+
v-model="tagsDialogOpen"
124+
:selected-level="selectedLevel"
125+
:selected-tags="selectedTags"
126+
:is-submitting="isSubmittingQuick"
127+
@update:selected-tags="selectedTags = $event"
128+
@submit="submitRatingWithTags"
129+
@tell-us-more="openDetailsDialogFromTags"
130+
/>
131+
117132
<GlobalContributeModal
118133
v-model="contributeDialogOpen"
119134
:contribute-form="contributeForm"
@@ -184,11 +199,19 @@ const sliderLabels = computed(() => {
184199
185200
.feedback-actions {
186201
display: flex;
187-
justify-content: flex-end;
202+
justify-content: space-between;
188203
gap: 0.35rem;
189204
margin-top: 1.2rem;
190205
}
191206
207+
.feedback-actions--center {
208+
justify-content: center;
209+
}
210+
211+
.feedback-actions--center {
212+
justify-content: center;
213+
}
214+
192215
.zoom-message {
193216
font-size: 0.8rem;
194217
text-align: center;

0 commit comments

Comments
 (0)