Skip to content

Commit 73d2ab7

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

7 files changed

Lines changed: 346 additions & 42 deletions

src/components/GlobalContributeModal.vue

Lines changed: 27 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,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)"
@@ -103,9 +128,8 @@ const toggleContributionType = (type: ContributionType, form: ContributeForm) =>
103128
<v-btn
104129
color="teal"
105130
variant="flat"
106-
:disabled="!canSubmit"
107131
:loading="isSubmitting"
108-
@click="emit('submit')"
132+
@click="onSubmit"
109133
>
110134
Submit
111135
</v-btn>

src/components/GlobalFeedbackDetailsModal.vue

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