nidus-sync/ts/rmo/view/ReportSubmitted.vue
Eli Ribble b9f2107c79
Some checks failed
/ golint (push) Failing after 9s
feat: show ErrorNotification in ReportSubmitted on submission failure
- Add ErrorNotification component above the contact form
- Replace console.error with user-visible error messages in
  handleSubmit for both HTTP error responses and exceptions
- Add isSubmitting ref with :disabled binding to prevent
  double-submission (consistent with Nuisance/Water forms)
- Clear errorMessage on each submission attempt

Issue: #8
2026-05-21 15:47:43 +00:00

369 lines
10 KiB
Vue

<style scoped>
.logo {
max-width: 200px;
height: auto;
}
</style>
<template>
<div class="container py-5">
<div class="row justify-content-center">
<div class="col-lg-8">
<!-- Success Card -->
<div class="card shadow-sm border-success mb-4">
<div class="card-header bg-success text-white">
<h3 class="my-2">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="currentColor"
class="bi bi-check-circle-fill me-2"
viewBox="0 0 16 16"
>
<path
d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z"
/>
</svg>
Report Successfully Submitted
</h3>
</div>
<div class="card-body p-4">
<div class="text-center mb-4">
<div class="alert alert-info py-3">
<strong>Your Report ID: </strong>
<span class="fs-4 fw-bold">{{ formatReportID(id) }}</span>
</div>
</div>
<hr class="my-4" />
<div>
<h4 class="mb-3">
<i class="bi bi-person-rolodex"></i>
Follow-up
</h4>
<p>
Please provide your contact information in case the district has
any follow-up questions.
</p>
<ErrorNotification
v-if="errorMessage"
:message="errorMessage"
@dismissed="errorMessage = ''"
/>
<form
id="notificationForm"
@submit.prevent="handleSubmit"
class="needs-validation"
>
<div class="mb-3">
<label class="form-label" for="name">Name</label>
<div class="input-group">
<span class="input-group-text">
<i class="bi bi-person-lines-fill"></i>
</span>
<input
v-model="formData.name"
type="text"
class="form-control"
id="name"
maxlength="100"
placeholder="Adam Smith"
/>
</div>
</div>
<div class="mb-3">
<label for="email" class="form-label">Email Address</label>
<div class="input-group">
<span class="input-group-text">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
fill="currentColor"
class="bi bi-envelope"
viewBox="0 0 16 16"
>
<path
d="M0 4a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V4Zm2-1a1 1 0 0 0-1 1v.217l7 4.2 7-4.2V4a1 1 0 0 0-1-1H2Zm13 2.383-4.708 2.825L15 11.105V5.383Zm-.034 6.876-5.64-3.471L8 9.583l-1.326-.795-5.64 3.47A1 1 0 0 0 2 13h12a1 1 0 0 0 .966-.741ZM1 11.105l4.708-2.897L1 5.383v5.722Z"
/>
</svg>
</span>
<input
v-model="formData.email"
type="email"
class="form-control"
id="email"
maxlength="200"
placeholder="your@email.com"
/>
</div>
</div>
<div class="mb-3">
<label for="phone" class="form-label">Phone Number</label>
<div class="input-group">
<span class="input-group-text">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
fill="currentColor"
class="bi bi-phone"
viewBox="0 0 16 16"
>
<path
d="M11 1a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h6zM5 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H5z"
/>
<path d="M8 14a1 1 0 1 0 0-2 1 1 0 0 0 0 2z" />
</svg>
</span>
<input
v-model="formData.phone"
type="tel"
class="form-control"
id="phone"
maxlength="100"
placeholder="(123) 456-7890"
/>
</div>
</div>
<div class="form-check mb-3 form-check">
<input
v-model="formData.can_sms"
class="form-check-input"
id="can_sms"
type="checkbox"
/>
<label class="form-check-label" for="can_sms">
This phone number can receive text messages
<Tooltip
placement="top"
title="By 'text messages' we specifically mean SMS/MMS messages. If it's a mobile or cellular phone, it should work. If it's a home or office phone, it may not."
>
<i class="bi bi-info-circle-fill text-primary ms-1"></i>
</Tooltip>
</label>
</div>
<div class="form-check mb-3 form-check">
<input
v-model="formData.consent"
class="form-check-input"
id="consent"
type="checkbox"
/>
<label class="form-check-label" for="consent">
I consent to being contacted at my email address or phone
number above just for the purposes of this report.
<Tooltip
placement="top"
title="We will never sell your information. We'll send you notifications, but only if you ask us to. We'll share your information with the district that is in change of mosquito control in the area you've reported, but not with anybody else."
>
<i class="bi bi-info-circle-fill text-primary ms-1"></i>
</Tooltip>
</label>
</div>
<div class="form-check mb-3 form-check">
<input
v-model="formData.notification"
class="form-check-input"
id="notification"
type="checkbox"
/>
<label class="form-check-label" for="notification">
I'd like to get updates about my report as it gets handled.
</label>
</div>
<div class="form-check mb-3 form-check">
<input
v-model="formData.subscribe"
class="form-check-input"
id="subscribe"
type="checkbox"
/>
<label class="form-check-label" for="subscribe">
<template v-if="!district">
I'd like to subscribe to periodic updates from my mosquito
control district.
</template>
<template v-else>
I'd like to subscribe to periodic updates from
{{ district.name }}
</template>
</label>
</div>
<button
type="submit"
class="btn btn-primary"
:disabled="isSubmitting"
>
Update report
</button>
</form>
</div>
<hr class="my-4" />
<!-- Status Check Section -->
<div class="mb-4">
<h4 class="mb-3">
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
fill="currentColor"
class="bi bi-search me-2"
viewBox="0 0 16 16"
>
<path
d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z"
/>
</svg>
Check Your Report Status
</h4>
<p>
You can check the status of your report at any time using your
Report ID.
</p>
<RouterLink
:to="routes.StatusByID(props.id)"
class="btn btn-outline-primary"
>
Check Status
</RouterLink>
</div>
<div class="row">
<div v-if="district" class="mb-4 text-center">
<p>Your report will be handled by</p>
<p>
<b>{{ district.name }}</b>
</p>
<a :href="district.url_website">
<img
class="logo"
:src="district.url_logo"
:alt="district.name + 'logo'"
/>
</a>
</div>
</div>
</div>
</div>
<!-- Navigation Links -->
<div class="text-center">
<RouterLink to="/" class="btn btn-outline-success">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
fill="currentColor"
class="bi bi-plus-circle me-1"
viewBox="0 0 16 16"
>
<path
d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"
/>
<path
d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"
/>
</svg>
Submit Another Report
</RouterLink>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from "vue";
import { computedAsync } from "@vueuse/core";
import { useRouter } from "vue-router";
import ErrorNotification from "@/rmo/components/ErrorNotification.vue";
import Tooltip from "@/components/Tooltip.vue";
import { formatReportID } from "@/format";
import { useRoutes } from "@/rmo/route/use";
import { useStoreDistrict } from "@/rmo/store/district";
import { useStorePublicReport } from "@/store/publicreport";
import type { District, PublicReport } from "@/type/api";
interface FormData {
can_sms: boolean;
consent: boolean;
email: string;
phone: string;
name: string;
notification: boolean;
subscribe: boolean;
}
interface Props {
id: string;
}
const props = defineProps<Props>();
const errorMessage = ref("");
const isSubmitting = ref(false);
const formData = ref<FormData>({
can_sms: true,
consent: true,
email: "",
phone: "",
name: "",
notification: false,
subscribe: false,
});
const router = useRouter();
const routes = useRoutes();
const storeDistrict = useStoreDistrict();
const storePublicReport = useStorePublicReport();
const report = computedAsync(async (): Promise<PublicReport | undefined> => {
return await storePublicReport.byID(props.id);
});
const district = computedAsync(async (): Promise<District | undefined> => {
if (!(report.value && report.value.district)) {
return undefined;
}
return await storeDistrict.byURI(report.value.district);
});
const handleSubmit = async () => {
isSubmitting.value = true;
errorMessage.value = "";
try {
const response = await fetch("/api/publicreport-notification", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
report_id: props.id,
...formData.value,
}),
});
if (response.ok) {
router.push(routes.RegisterNotificationsComplete(props.id));
} else {
errorMessage.value = "Something went wrong. Your request could not be completed. Please try again.";
}
} catch (error) {
errorMessage.value =
error instanceof Error ? error.message : "Upload failed";
} finally {
isSubmitting.value = false;
}
};
onMounted(() => {});
</script>