Skip to content
Open
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
25 changes: 22 additions & 3 deletions src/components/contact/Header.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,17 @@
:id="f.id"
type="text"
class="form-control"
:class="{ 'is-invalid': errors[f.id] }"
:name="f.id"
required
@input="clearError(f.id)"
/>
<div
v-if="errors[f.id]"
class="field-error"
>
{{ errors[f.id] }}
</div>
</div>
</div>

Expand All @@ -54,18 +62,25 @@
v-if="f.type === 'textarea'"
:id="f.id"
class="form-control"
:class="{ 'is-invalid': errors[f.id] }"
:name="f.id"
rows="4"
required
@input="clearError(f.id)"
/>
<input
v-else
:id="f.id"
:type="f.type"
class="form-control"
:class="{ 'is-invalid': errors[f.id] }"
:name="f.id"
required
@input="clearError(f.id)"
/>
<div v-if="errors[f.id]" class="field-error">
{{ errors[f.id] }}
</div>
</div>
<button
type="submit"
Expand All @@ -87,6 +102,7 @@
import identify from "~/utils/identify"
import { useGtm } from "@gtm-support/vue-gtm"
import { $fetch } from "~/utils/fetch"
import { useFormErrors } from "~/composables/useFormErrors"
import Squared from "~/components/layout/Squared.vue"

const props = defineProps<{ routePath: string }>()
Expand Down Expand Up @@ -127,13 +143,16 @@
},
]

const { errors, clearError, validate } = useFormErrors(
Object.fromEntries(fields.map((f) => [f.id, f.label])),
)

async function onSubmit() {
const form = formRef.value as HTMLFormElement
message.value = ""

if (!form.checkValidity()) {
form.reportValidity()
message.value = "Invalid form: Please review the fields."
if (!validate(form)) {
message.value = "Please correct the highlighted fields below."
return
}

Expand Down
50 changes: 47 additions & 3 deletions src/components/partners/BecomeAPartner.vue
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,19 @@
:id="fields[0].id"
type="text"
class="form-control"
:class="{
'is-invalid': errors[fields[0].id],
}"
:name="fields[0].id"
required
@input="clearError(fields[0].id)"
/>
<div
v-if="errors[fields[0].id]"
class="field-error"
>
{{ errors[fields[0].id] }}
</div>
</div>

<div class="form-row">
Expand All @@ -59,9 +69,19 @@
:id="f.id"
type="text"
class="form-control"
:class="{
'is-invalid': errors[f.id],
}"
:name="f.id"
required
@input="clearError(f.id)"
/>
<div
v-if="errors[f.id]"
class="field-error"
>
{{ errors[f.id] }}
</div>
</div>
</div>

Expand All @@ -82,18 +102,28 @@
v-if="f.type === 'textarea'"
:id="f.id"
class="form-control"
:class="{ 'is-invalid': errors[f.id] }"
:name="f.id"
rows="4"
:required="f.required"
@input="clearError(f.id)"
/>
<input
v-else
:id="f.id"
:type="f.type"
class="form-control"
:class="{ 'is-invalid': errors[f.id] }"
:name="f.id"
:required="f.required"
@input="clearError(f.id)"
/>
<div
v-if="errors[f.id]"
class="field-error"
>
{{ errors[f.id] }}
</div>
</div>

<div class="form-group">
Expand Down Expand Up @@ -139,14 +169,24 @@
<input
id="consent"
type="checkbox"
:class="{
'is-invalid': errors.consent,
}"
name="consent"
required
@change="clearError('consent')"
/>
<label for="consent"
>I agree to receive other
communications from Kestra.</label
>
</div>
<div
v-if="errors.consent"
class="field-error"
>
{{ errors.consent }}
</div>
<p class="mt-3">
By clicking submit below, you consent to
allow Kestra to store and process the
Expand Down Expand Up @@ -177,6 +217,7 @@
import identify from "~/utils/identify"
import { useGtm } from "@gtm-support/vue-gtm"
import { $fetch } from "~/utils/fetch"
import { useFormErrors } from "~/composables/useFormErrors"

const props = defineProps<{ routePath: string }>()

Expand Down Expand Up @@ -235,13 +276,16 @@
},
]

const { errors, clearError, validate } = useFormErrors(
Object.fromEntries(fields.map((f) => [f.id, f.label])),
)

async function onSubmit() {
const form = formRef.value as HTMLFormElement
message.value = ""

if (!form.checkValidity()) {
form.reportValidity()
message.value = "Invalid form: Please review the fields."
if (!validate(form)) {
message.value = "Please correct the highlighted fields below."
return
}

Expand Down
18 changes: 18 additions & 0 deletions src/components/partners/style/form.scss
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,20 @@
outline: none;
border-color: var(--ks-border-active);
}

&.is-invalid,
&.is-invalid:focus {
border-color: var(--ks-border-alert-danger);
// Suppress Bootstrap's validation icon and the padding it reserves.
background-image: none;
padding-right: 12px;
}
}

.field-error {
margin-top: 6px;
font-size: $font-size-sm;
color: var(--ks-content-alert-danger);
}

.checkbox-options {
Expand Down Expand Up @@ -79,6 +93,10 @@
font-size: 12px;
}
}

&.is-invalid {
border-color: var(--ks-border-alert-danger);
}
}

label {
Expand Down
40 changes: 38 additions & 2 deletions src/components/preview-access/Features.vue
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,14 @@
name="firstname"
type="text"
class="form-control"
:class="{ 'is-invalid': errors.firstname }"
id="firstname"
required
@input="clearError('firstname')"
/>
<div v-if="errors.firstname" class="field-error">
{{ errors.firstname }}
</div>
</div>
<div class="col-6">
<label for="lastname">
Expand All @@ -91,9 +96,14 @@
name="lastname"
type="text"
class="form-control"
:class="{ 'is-invalid': errors.lastname }"
id="lastname"
required
@input="clearError('lastname')"
/>
<div v-if="errors.lastname" class="field-error">
{{ errors.lastname }}
</div>
</div>
<div class="col-12 mt-3">
<label for="email">
Expand All @@ -104,9 +114,14 @@
name="email"
type="email"
class="form-control"
:class="{ 'is-invalid': errors.email }"
id="email"
required
@input="clearError('email')"
/>
<div v-if="errors.email" class="field-error">
{{ errors.email }}
</div>
</div>
<div class="col-12 mt-4 pb-5 d-flex justify-content-center">
<button type="submit" class="btn btn-primary w-100">
Expand All @@ -126,6 +141,7 @@
import identify from "~/utils/identify"
import { useGtm } from "@gtm-support/vue-gtm"
import { $fetch } from "~/utils/fetch"
import { useFormErrors } from "~/composables/useFormErrors"
import calendarMonthImage from "./images/calendar_month.png"
import smallCloudImage from "./images/small_cloud.png"
import hammerWrenchImage from "./images/hammer_wrench.png"
Expand All @@ -141,16 +157,22 @@
const validMessage = ref("")
const message = ref("")

const { errors, clearError, validate } = useFormErrors({
firstname: "first name",
lastname: "last name",
email: "company email",
})

const hubSpotFormId = "230d0ed2-2484-4e9e-86c6-135a6398fac5"

const onSubmit = async (e: Event) => {
e.preventDefault()
e.stopPropagation()
const form = formRef.value as HTMLFormElement
const hsq = ((window as any)._hsq = (window as any)._hsq || [])
if (!form.checkValidity()) {
if (!validate(form)) {
valid.value = false
message.value = "Invalid form, please review the fields."
message.value = "Please correct the highlighted fields below."
} else {
hsq.push([
"identify",
Expand Down Expand Up @@ -228,6 +250,20 @@
</script>

<style scoped lang="scss">
.form-control.is-invalid,
.form-control.is-invalid:focus {
border-color: var(--ks-border-alert-danger);
// Suppress Bootstrap's validation icon and the padding it reserves.
background-image: none;
padding-right: $input-padding-x;
}

.field-error {
margin-top: 6px;
font-size: $font-size-sm;
color: var(--ks-content-alert-danger);
}

.livedemo {
& img {
width: 180px;
Expand Down
24 changes: 24 additions & 0 deletions src/composables/useFormErrors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { ref } from "vue"
import { getFormErrors } from "~/utils/formValidation"

// Per-field validation state for the HubSpot-backed forms: `validate` records
// a specific message per invalid field and focuses the first one, `clearError`
// dismisses a field's message as the user edits it.
export function useFormErrors(labels: Record<string, string> = {}) {
const errors = ref<Record<string, string>>({})

function clearError(id: string) {
delete errors.value[id]
}

function validate(form: HTMLFormElement): boolean {
errors.value = getFormErrors(form, labels)
if (Object.keys(errors.value).length === 0) {
return true
}
;(form.querySelector(":invalid") as HTMLElement | null)?.focus()
return false
}

return { errors, clearError, validate }
}
Loading
Loading