feat(forms): add inline blur validation for name and email

This commit is contained in:
Jennie Robinson Faber 2026-05-23 16:09:36 +01:00
parent 1079e8212f
commit 10f8cab6e3
3 changed files with 66 additions and 0 deletions

View file

@ -32,7 +32,10 @@
class="form-input" class="form-input"
type="text" type="text"
required required
@blur="validateName"
@input="if (fieldErrors.name) fieldErrors.name = ''"
> >
<p v-if="fieldErrors.name" class="field-error">{{ fieldErrors.name }}</p>
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="form-label" for="accept-email">Email</label> <label class="form-label" for="accept-email">Email</label>
@ -201,6 +204,13 @@ const form = reactive({
agreedToGuidelines: false, agreedToGuidelines: false,
}); });
// Inline blur validation (UI feedback only does not block submission)
const fieldErrors = reactive({ name: "" });
const validateName = () => {
fieldErrors.name = form.name.trim() ? "" : "Please enter your name.";
};
const isFormValid = computed(() => { const isFormValid = computed(() => {
return ( return (
form.name && form.name &&
@ -447,6 +457,12 @@ textarea.form-input {
line-height: 1.4; line-height: 1.4;
} }
.field-error {
font-size: 11px;
color: var(--ember);
margin-top: 4px;
}
/* ---- CIRCLE RADIOS ---- */ /* ---- CIRCLE RADIOS ---- */
.circle-radios { .circle-radios {
display: grid; display: grid;

View file

@ -128,7 +128,10 @@
class="form-input" class="form-input"
type="text" type="text"
required required
@blur="validateName"
@input="if (fieldErrors.name) fieldErrors.name = ''"
> >
<p v-if="fieldErrors.name" class="field-error">{{ fieldErrors.name }}</p>
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="form-label" for="join-email">Email</label> <label class="form-label" for="join-email">Email</label>
@ -139,7 +142,10 @@
type="email" type="email"
placeholder="you@example.com" placeholder="you@example.com"
required required
@blur="validateEmail"
@input="if (fieldErrors.email) fieldErrors.email = ''"
> >
<p v-if="fieldErrors.email" class="field-error">{{ fieldErrors.email }}</p>
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="form-label">Circle</label> <label class="form-label">Circle</label>
@ -346,6 +352,23 @@ const errorMessage = ref("");
const successMessage = ref(""); const successMessage = ref("");
const cadence = ref("monthly"); // 'monthly' | 'annual' const cadence = ref("monthly"); // 'monthly' | 'annual'
// Inline blur validation (UI feedback only does not block submission)
const fieldErrors = reactive({ name: "", email: "" });
const validateName = () => {
fieldErrors.name = form.name.trim() ? "" : "Please enter your name.";
};
const validateEmail = () => {
const value = form.email.trim();
if (!value) {
fieldErrors.email = "";
return;
}
const ok = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
fieldErrors.email = ok ? "" : "Please enter a valid email address.";
};
// Flow overlay state drives the post-submit full-viewport UI. // Flow overlay state drives the post-submit full-viewport UI.
// 'idle' = overlay hidden; user is editing the form. // 'idle' = overlay hidden; user is editing the form.
// 'creating-customer' | 'opening-payment' | 'processing-payment' // 'creating-customer' | 'opening-payment' | 'processing-payment'
@ -762,6 +785,11 @@ onUnmounted(() => {
.form-input::placeholder { .form-input::placeholder {
color: var(--text-faint); color: var(--text-faint);
} }
.field-error {
font-size: 11px;
color: var(--ember);
margin-top: 4px;
}
/* ---- CIRCLE RADIOS ---- */ /* ---- CIRCLE RADIOS ---- */
.circle-radios { .circle-radios {

View file

@ -173,9 +173,12 @@
type="email" type="email"
placeholder="you@example.com" placeholder="you@example.com"
autofocus autofocus
@blur="validateNewEmail"
@input="if (fieldErrors.email) fieldErrors.email = ''"
@keydown.enter="handleUpdateEmail" @keydown.enter="handleUpdateEmail"
@keydown.escape="cancelEmailEdit" @keydown.escape="cancelEmailEdit"
> >
<p v-if="fieldErrors.email" class="field-error">{{ fieldErrors.email }}</p>
</div> </div>
<div class="email-edit-actions"> <div class="email-edit-actions">
<button <button
@ -317,6 +320,19 @@ const showEmailEdit = ref(false);
const newEmail = ref(""); const newEmail = ref("");
const isUpdatingEmail = ref(false); const isUpdatingEmail = ref(false);
// Inline blur validation (UI feedback only does not block submission)
const fieldErrors = reactive({ email: "" });
const validateNewEmail = () => {
const value = newEmail.value.trim();
if (!value) {
fieldErrors.email = "";
return;
}
const ok = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
fieldErrors.email = ok ? "" : "Please enter a valid email address.";
};
// Payment history state // Payment history state
const paymentHistory = ref([]); const paymentHistory = ref([]);
const paymentHistoryLoading = ref(false); const paymentHistoryLoading = ref(false);
@ -516,6 +532,7 @@ const handleUpdateCircle = async () => {
const cancelEmailEdit = () => { const cancelEmailEdit = () => {
showEmailEdit.value = false; showEmailEdit.value = false;
newEmail.value = ""; newEmail.value = "";
fieldErrors.email = "";
}; };
const handleUpdateEmail = async () => { const handleUpdateEmail = async () => {
@ -823,6 +840,11 @@ const confirmCancelMembership = async () => {
.email-edit .field input:focus { .email-edit .field input:focus {
border-color: var(--candle); border-color: var(--candle);
} }
.email-edit .field .field-error {
font-size: 11px;
color: var(--ember);
margin-top: 4px;
}
.email-edit-actions { .email-edit-actions {
display: flex; display: flex;
gap: 8px; gap: 8px;