Skip to content
115 changes: 115 additions & 0 deletions src/components/GlobalContributeModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
<script setup lang="ts">
import type { ContributeForm, ContributionType } from '../composables/useGlobalContribute'
import PersonalDetailsFields from './PersonalDetailsFields.vue'

interface Props {
modelValue: boolean
contributeForm: ContributeForm
contributionOptions: Array<{ value: ContributionType; label: string; description: string }>
canSubmit: boolean
isSubmitting: boolean
}

interface Emits {
'update:modelValue': [value: boolean]
'update:form': [field: keyof ContributeForm, value: any]
submit: []
}

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

const toggleContributionType = (type: ContributionType, form: ContributeForm) => {
const index = form.contributionTypes.indexOf(type)
const newTypes = [...form.contributionTypes]
if (index >= 0) {
newTypes.splice(index, 1)
} else {
newTypes.push(type)
}
emit('update:form', 'contributionTypes', newTypes)
}
</script>

<template>
<v-dialog
:model-value="modelValue"
@update:model-value="emit('update:modelValue', $event)"
max-width="550"
>
<v-card>
<v-card-title class="text-h5">Become a Contributor</v-card-title>
<v-card-subtitle class="text-body2 px-6 pb-2 text-wrap">
To carry FTW forward will require the collective talent and resources of our community. We
welcome your contributions and partnership!
</v-card-subtitle>

<v-card-text>
<div class="mb-3">
<p class="text-xs font-weight-bold mb-2">How would you like to contribute? *</p>
<div class="d-flex flex-column">
<v-checkbox
v-for="option in contributionOptions"
:key="option.value"
:model-value="contributeForm.contributionTypes.includes(option.value)"
:label="option.label"
@update:model-value="toggleContributionType(option.value, contributeForm)"
class="pa-0 ma-0"
density="compact"
hide-details
></v-checkbox>
</div>
</div>

<v-textarea
:model-value="contributeForm.resourceLink"
@update:model-value="emit('update:form', 'resourceLink', $event)"
label="Do you have Field Boundaries data or a model to share?"
hint="Share a Link or describe your resource"
variant="outlined"
rows="2"
auto-grow
class="mb-2"
density="compact"
></v-textarea>

<v-textarea
:model-value="contributeForm.additionalInfo"
@update:model-value="emit('update:form', 'additionalInfo', $event)"
label="Anything else you would like us to know?"
variant="outlined"
rows="3"
auto-grow
class="mb-2"
density="compact"
></v-textarea>

<PersonalDetailsFields
:name="contributeForm.name"
:email="contributeForm.email"
:organization="contributeForm.organization"
required
density="compact"
field-spacing="mb-2"
@update:name="emit('update:form', 'name', $event)"
@update:email="emit('update:form', 'email', $event)"
@update:organization="emit('update:form', 'organization', $event)"
/>
</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"
:disabled="!canSubmit"
:loading="isSubmitting"
@click="emit('submit')"
>
Submit
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
84 changes: 84 additions & 0 deletions src/components/GlobalFeedbackDetailsModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<script setup lang="ts">
import type { DetailedFeedbackForm } from '../composables/useGlobalFeedback'
import PersonalDetailsFields from './PersonalDetailsFields.vue'

interface Props {
modelValue: boolean
detailsForm: DetailedFeedbackForm
canSubmit: boolean
isSubmitting: boolean
}

interface Emits {
'update:modelValue': [value: boolean]
'update:form': [field: keyof DetailedFeedbackForm, value: string]
submit: []
}

defineProps<Props>()
const emit = defineEmits<Emits>()
</script>

<template>
<v-dialog
:model-value="modelValue"
@update:model-value="emit('update:modelValue', $event)"
max-width="550"
>
<v-card>
<v-card-title class="text-h5">Tell Us More</v-card-title>
<v-card-subtitle class="text-body2 px-6 pb-2 text-wrap">
We are just getting started! We know there is room to improve and we need your feedback!
</v-card-subtitle>

<v-card-text>
<v-textarea
:model-value="detailsForm.qualityFeedback"
@update:model-value="emit('update:form', 'qualityFeedback', $event)"
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"
></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?"
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"
></v-textarea>

<PersonalDetailsFields
:name="detailsForm.name"
:email="detailsForm.email"
:organization="detailsForm.organization"
@update:name="emit('update:form', 'name', $event)"
@update:email="emit('update:form', 'email', $event)"
@update:organization="emit('update:form', 'organization', $event)"
/>
</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"
:disabled="!canSubmit"
:loading="isSubmitting"
@click="emit('submit')"
>
Submit
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
101 changes: 35 additions & 66 deletions src/components/GlobalFeedbackWidget.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import { computed } from 'vue'
import useMap from '../composables/useMap'
import { geoJsonResults } from '../composables/useMap'
import useSettings from '../composables/useSettings'
import GlobalContributeModal from './GlobalContributeModal.vue'
import GlobalFeedbackDetailsModal from './GlobalFeedbackDetailsModal.vue'
import useGlobalFeedback from '../composables/useGlobalFeedback'
import useGlobalContribute from '../composables/useGlobalContribute'

const { settings } = useSettings()
const { map } = useMap()
Expand All @@ -21,11 +24,21 @@ const {
isSubmittingQuick,
isSubmittingDetails,
openDetailsDialog,
closeDetailsDialog,
submitQuickFeedback,
submitDetailedFeedback,
} = useGlobalFeedback(map)

const {
CONTRIBUTION_OPTIONS,
contributeDialogOpen,
contributeForm,
canSubmit: canSubmitContribute,
isSubmitting: isSubmittingContribute,
openContributeDialog,
submitContribute,
updateFormField,
} = useGlobalContribute()

const sliderLabels = computed(() => {
return Object.fromEntries(options.map((opt, i) => [i, opt.title]))
})
Expand Down Expand Up @@ -61,6 +74,9 @@ const sliderLabels = computed(() => {
</div>

<div class="feedback-actions mt-3">
<v-btn variant="outlined" color="teal" size="small" @click="openContributeDialog">
Contribute
</v-btn>
<v-btn
variant="outlined"
color="teal"
Expand Down Expand Up @@ -89,71 +105,24 @@ const sliderLabels = computed(() => {
</v-card-text>
</v-card>

<v-dialog v-model="detailsDialogOpen" max-width="530">
<v-card>
<v-card-title class="text-h5">Tell Us More</v-card-title>
<v-card-subtitle class="text-body2 px-6 pb-2 text-wrap">
We are just getting started! We know there is room to improve and we need your feedback!
</v-card-subtitle>

<v-card-text>
<v-textarea
v-model="detailsForm.qualityFeedback"
label="What was good/bad? How must the field boundaries improve to be useful to you?"
variant="outlined"
rows="4"
auto-grow
required
class="mb-3"
></v-textarea>

<v-textarea
v-model="detailsForm.useCase"
label="How would you like to use field boundaries? Tell us about your use case."
variant="outlined"
rows="3"
auto-grow
required
class="mb-3"
></v-textarea>

<v-text-field
v-model="detailsForm.name"
label="Name"
variant="outlined"
class="mb-3"
></v-text-field>

<v-text-field
v-model="detailsForm.email"
type="email"
label="Email"
variant="outlined"
class="mb-3"
></v-text-field>

<v-text-field
v-model="detailsForm.organization"
label="Organization"
variant="outlined"
></v-text-field>
</v-card-text>

<v-card-actions>
<v-spacer></v-spacer>
<v-btn variant="text" @click="closeDetailsDialog">Cancel</v-btn>
<v-btn
color="teal"
variant="flat"
:disabled="!canSubmitDetailed"
:loading="isSubmittingDetails"
@click="submitDetailedFeedback"
>
Submit
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<GlobalFeedbackDetailsModal
v-model="detailsDialogOpen"
:details-form="detailsForm"
:can-submit="canSubmitDetailed"
:is-submitting="isSubmittingDetails"
@update:form="(field, value) => (detailsForm[field] = value)"
@submit="submitDetailedFeedback"
/>

<GlobalContributeModal
v-model="contributeDialogOpen"
:contribute-form="contributeForm"
:contribution-options="CONTRIBUTION_OPTIONS"
:can-submit="canSubmitContribute"
:is-submitting="isSubmittingContribute"
@update:form="updateFormField"
@submit="submitContribute"
/>
</div>
</template>

Expand Down
61 changes: 61 additions & 0 deletions src/components/PersonalDetailsFields.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<script setup lang="ts">
import { computed } from 'vue'
import { isValidEmail } from '../functions/feedback-utils'

interface Props {
name: string
email: string
organization: string
required?: boolean
density?: 'default' | 'comfortable' | 'compact'
fieldSpacing?: string
}

const props = withDefaults(defineProps<Props>(), {
required: false,
density: 'default',
fieldSpacing: 'mb-3',
})

interface Emits {
'update:name': [value: string]
'update:email': [value: string]
'update:organization': [value: string]
}

const emit = defineEmits<Emits>()

const emailError = computed(() =>
props.email && !isValidEmail(props.email) ? 'Please enter a valid email address.' : undefined,
)
</script>

<template>
<v-text-field
:model-value="name"
@update:model-value="emit('update:name', ($event ?? '').trim())"
:label="required ? 'Name *' : 'Name'"
variant="outlined"
:density="density"
:class="fieldSpacing"
></v-text-field>

<v-text-field
:model-value="email"
@update:model-value="emit('update:email', ($event ?? '').trim())"
type="email"
:label="required ? 'Email *' : 'Email'"
variant="outlined"
:density="density"
:class="fieldSpacing"
:error-messages="emailError"
></v-text-field>

<v-text-field
:model-value="organization"
@update:model-value="emit('update:organization', ($event ?? '').trim())"
label="Organization"
variant="outlined"
:density="density"
></v-text-field>
</template>
Loading
Loading