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
35 changes: 27 additions & 8 deletions src/components/GlobalContributeModal.vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import type { ContributeForm, ContributionType } from '../composables/useGlobalContribute'
import PersonalDetailsFields from './PersonalDetailsFields.vue'

Expand All @@ -16,9 +17,29 @@ interface Emits {
submit: []
}

defineProps<Props>()
const props = defineProps<Props>()
const emit = defineEmits<Emits>()

const submitted = ref(false)

watch(
() => props.modelValue,
(open) => {
if (open) submitted.value = false
},
)

const contributionTypesError = computed(() =>
submitted.value && props.contributeForm.contributionTypes.length === 0
? 'Please select at least one option.'
: undefined,
)

function onSubmit() {
submitted.value = true
emit('submit')
}

const toggleContributionType = (type: ContributionType, form: ContributeForm) => {
const index = form.contributionTypes.indexOf(type)
const newTypes = [...form.contributionTypes]
Expand Down Expand Up @@ -59,6 +80,9 @@ const toggleContributionType = (type: ContributionType, form: ContributeForm) =>
hide-details
></v-checkbox>
</div>
<p v-if="contributionTypesError" class="text-error text-caption mt-2 mb-0">
{{ contributionTypesError }}
</p>
</div>

<v-textarea
Expand Down Expand Up @@ -89,6 +113,7 @@ const toggleContributionType = (type: ContributionType, form: ContributeForm) =>
:email="contributeForm.email"
:organization="contributeForm.organization"
required
:submitted="submitted"
density="compact"
field-spacing="mb-2"
@update:name="emit('update:form', 'name', $event)"
Expand All @@ -100,13 +125,7 @@ const toggleContributionType = (type: ContributionType, form: ContributeForm) =>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn variant="text" @click="emit('update:modelValue', false)">Cancel</v-btn>
<v-btn
color="teal"
variant="flat"
:disabled="!canSubmit"
:loading="isSubmitting"
@click="emit('submit')"
>
<v-btn color="teal" variant="flat" :loading="isSubmitting" @click="onSubmit">
Submit
</v-btn>
</v-card-actions>
Expand Down
44 changes: 32 additions & 12 deletions src/components/GlobalFeedbackDetailsModal.vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import type { DetailedFeedbackForm } from '../composables/useGlobalFeedback'
import PersonalDetailsFields from './PersonalDetailsFields.vue'

Expand All @@ -15,8 +16,32 @@ interface Emits {
submit: []
}

defineProps<Props>()
const props = defineProps<Props>()
const emit = defineEmits<Emits>()

const submitted = ref(false)

watch(
() => props.modelValue,
(open) => {
if (open) submitted.value = false
},
)

const qualityFeedbackError = computed(() =>
submitted.value && !props.detailsForm.qualityFeedback.trim()
? 'This field is required.'
: undefined,
)

const useCaseError = computed(() =>
submitted.value && !props.detailsForm.useCase.trim() ? 'This field is required.' : undefined,
)

function onSubmit() {
submitted.value = true
emit('submit')
}
</script>

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

<v-textarea
:model-value="detailsForm.useCase"
@update:model-value="emit('update:form', 'useCase', $event)"
label="How would you like to use field boundaries?"
label="How would you like to use field boundaries? *"
hint="Describe your specific use case and how field boundaries could be more useful to you"
variant="outlined"
rows="3"
auto-grow
required
class="mb-3"
:error-messages="useCaseError"
></v-textarea>

<PersonalDetailsFields
:name="detailsForm.name"
:email="detailsForm.email"
:organization="detailsForm.organization"
:submitted="submitted"
@update:name="emit('update:form', 'name', $event)"
@update:email="emit('update:form', 'email', $event)"
@update:organization="emit('update:form', 'organization', $event)"
Expand All @@ -69,13 +95,7 @@ const emit = defineEmits<Emits>()
<v-card-actions>
<v-spacer></v-spacer>
<v-btn variant="text" @click="emit('update:modelValue', false)">Cancel</v-btn>
<v-btn
color="teal"
variant="flat"
:disabled="!canSubmit"
:loading="isSubmitting"
@click="emit('submit')"
>
<v-btn color="teal" variant="flat" :loading="isSubmitting" @click="onSubmit">
Submit
</v-btn>
</v-card-actions>
Expand Down
130 changes: 130 additions & 0 deletions src/components/GlobalFeedbackTagsModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
<script setup lang="ts">
import type { FeedbackRating, RatingTag, TagOption } from '../composables/useGlobalFeedback'
import { RATING_TAGS } from '../composables/useGlobalFeedback'
import { computed, ref, watch } from 'vue'

interface Props {
modelValue: boolean
selectedLevel: FeedbackRating | null
selectedTags: RatingTag[]
isSubmitting: boolean
}

interface Emits {
'update:modelValue': [value: boolean]
'update:selectedTags': [tags: RatingTag[]]
submit: []
tellUsMore: []
}

const props = defineProps<Props>()
const emit = defineEmits<Emits>()

const submitted = ref(false)

watch(
() => props.modelValue,
(open) => {
if (open) submitted.value = false
},
)

const availableTags = computed<TagOption[]>(() => {
if (!props.selectedLevel) return []
return RATING_TAGS[props.selectedLevel]
})

const ratingLabel = computed(() => {
if (props.selectedLevel === 3) return 'Good'
if (props.selectedLevel === 2) return 'Acceptable'
if (props.selectedLevel === 1) return 'Poor'
return ''
})

const canSubmit = computed(() => props.selectedTags.length > 0)

const pendingAction = ref<'submit' | 'tellUsMore' | null>(null)

watch(
() => props.isSubmitting,
(val) => {
if (!val) pendingAction.value = null
},
)

function onTellUsMore() {
submitted.value = true
pendingAction.value = 'tellUsMore'
emit('tellUsMore')
}

function onSubmit() {
submitted.value = true
pendingAction.value = 'submit'
emit('submit')
}

function toggleTag(tag: RatingTag) {
const current = [...props.selectedTags]
const idx = current.indexOf(tag)
if (idx >= 0) {
current.splice(idx, 1)
} else {
current.push(tag)
}
emit('update:selectedTags', current)
}
</script>

<template>
<v-dialog
:model-value="modelValue"
@update:model-value="emit('update:modelValue', $event)"
max-width="450"
>
<v-card>
<v-card-title class="text-h5">What did you notice?</v-card-title>
<v-card-subtitle class="text-body2 px-6 pb-2 text-wrap">
You rated this area as <strong>{{ ratingLabel }}</strong
>. What best describes what you observed?
</v-card-subtitle>

<v-card-text>
<p v-if="submitted && !canSubmit" class="text-error text-caption mb-3 mt-0">
Please select at least one option.
</p>
<v-checkbox
v-for="tag in availableTags"
:key="tag.value"
:model-value="selectedTags.includes(tag.value)"
:label="tag.label"
color="teal"
density="compact"
hide-details
@update:model-value="toggleTag(tag.value)"
/>
</v-card-text>

<v-card-actions>
<v-spacer></v-spacer>
<v-btn variant="text" @click="emit('update:modelValue', false)">Cancel</v-btn>
<v-btn
color="teal"
variant="flat"
:loading="isSubmitting && pendingAction === 'tellUsMore'"
@click="onTellUsMore"
>
Tell Us More
</v-btn>
<v-btn
color="teal"
variant="flat"
:loading="isSubmitting && pendingAction === 'submit'"
@click="onSubmit"
>
Submit
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
45 changes: 32 additions & 13 deletions src/components/GlobalFeedbackWidget.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { geoJsonResults } from '../composables/useMap'
import useSettings from '../composables/useSettings'
import GlobalContributeModal from './GlobalContributeModal.vue'
import GlobalFeedbackDetailsModal from './GlobalFeedbackDetailsModal.vue'
import GlobalFeedbackTagsModal from './GlobalFeedbackTagsModal.vue'
import useGlobalFeedback from '../composables/useGlobalFeedback'
import useGlobalContribute from '../composables/useGlobalContribute'

Expand All @@ -18,13 +19,16 @@ const {
selectedLevel,
detailsDialogOpen,
detailsForm,
tagsDialogOpen,
selectedTags,
canProvideFeedback,
zoomGateMessage,
isMediumZoom,
canSubmitDetailed,
isSubmittingQuick,
isSubmittingDetails,
openDetailsDialog,
openDetailsDialogFromTags,
submitQuickFeedback,
submitRatingWithTags,
submitDetailedFeedback,
} = useGlobalFeedback(map)

Expand Down Expand Up @@ -77,15 +81,6 @@ const sliderLabels = computed(() => {
<v-btn variant="outlined" color="teal" size="small" @click="openContributeDialog">
Contribute
</v-btn>
<v-btn
variant="outlined"
color="teal"
size="small"
:disabled="!selectedLevel"
@click="openDetailsDialog"
>
Tell Us More
</v-btn>
<v-btn
variant="flat"
color="teal"
Expand All @@ -100,7 +95,17 @@ const sliderLabels = computed(() => {
</template>

<template v-else>
<div class="zoom-message">{{ zoomGateMessage }}</div>
<div class="zoom-message">
<template v-if="isMediumZoom">
Zoom in more to see <strong>all</strong> fields and to give feedback.
</template>
<template v-else> Zoom in to see fields and to give feedback. </template>
</div>
<div class="feedback-actions feedback-actions--center mt-3">
<v-btn variant="outlined" color="teal" size="small" @click="openContributeDialog">
Contribute
</v-btn>
</div>
</template>
</v-card-text>
</v-card>
Expand All @@ -114,6 +119,16 @@ const sliderLabels = computed(() => {
@submit="submitDetailedFeedback"
/>

<GlobalFeedbackTagsModal
v-model="tagsDialogOpen"
:selected-level="selectedLevel"
:selected-tags="selectedTags"
:is-submitting="isSubmittingQuick"
@update:selected-tags="selectedTags = $event"
@submit="submitRatingWithTags"
@tell-us-more="openDetailsDialogFromTags"
/>

<GlobalContributeModal
v-model="contributeDialogOpen"
:contribute-form="contributeForm"
Expand Down Expand Up @@ -184,11 +199,15 @@ const sliderLabels = computed(() => {

.feedback-actions {
display: flex;
justify-content: flex-end;
justify-content: space-between;
gap: 0.35rem;
margin-top: 1.2rem;
}

.feedback-actions--center {
justify-content: center;
}

.zoom-message {
font-size: 0.8rem;
text-align: center;
Expand Down
Loading
Loading