refactor(frontend): migrate email fields to EmailInput component

Replace raw <Input type="email"> with <EmailInput> in login (register + login), account (gift card recipient), BookingFlow (guest email), and GiftCardsManagement (admin gift card email).

Removes 30-line validateEmail() function from login page (now handled by EmailInput internally).

Removes validateEmailFormat() from BookingFlow (EmailInput handles format validation; debounced backend email check preserved via onvaluechange/onblur).

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-06-12 10:51:07 +01:00
co-authored by Sisyphus
parent 1cadbbe968
commit 18eaa00479
4 changed files with 48 additions and 100 deletions
@@ -4,6 +4,7 @@
import { Button } from '$lib/components/ui/button';
import * as Card from '$lib/components/ui/card';
import { Input } from '$lib/components/ui/input';
import { EmailInput } from '$lib/components/ui/email-input';
import * as Modal from '$lib/components/ui/dialog';
import { Skeleton } from '$lib/components/ui/skeleton';
@@ -1765,11 +1766,10 @@
Recipient Email Address (Optional)
{/if}
</label>
<Input
<EmailInput
id="generate-email"
type="email"
placeholder="e.g. customer@example.com"
bind:value={generateEmail}
placeholder="e.g. customer@example.com"
/>
{#if selectedCustomer?.email && !generateEmail.trim()}
<p class="text-xs text-gray-500 italic">Defaults to: {selectedCustomer.email}</p>
@@ -2,6 +2,7 @@
import { Button } from '$lib/components/ui/button/index.js';
import * as Card from '$lib/components/ui/card/index.js';
import { Input } from '$lib/components/ui/input/index.js';
import { EmailInput } from '$lib/components/ui/email-input/index.js';
import { Label } from '$lib/components/ui/label/index.js';
import { Textarea } from '$lib/components/ui/textarea/index.js';
import CharCounter from '$lib/components/ui/CharCounter.svelte';
@@ -106,18 +107,6 @@
isValidUKPhone(customerInfo.phone)
);
function validateEmailFormat(email: string) {
if (!email) {
emailError = '';
return;
}
if (!/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(email)) {
emailError = 'Please enter a valid email address';
} else {
emailError = '';
}
}
async function checkEmailExists(email: string) {
if (!email || !/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(email)) {
emailSuggestion = null;
@@ -1713,18 +1702,17 @@
</div>
<div class="space-y-2">
<Label for="email">Email *</Label>
<Input
<EmailInput
id="email"
type="email"
bind:value={customerInfo.email}
placeholder="Enter your email"
onblur={() => {
validateEmailFormat(customerInfo.email);
required
onvaluechange={() => {
emailSuggestion = null;
debouncedEmailCheck();
}}
oninput={() => {
emailSuggestion = null;
emailError = '';
onerrorchange={(err) => { emailError = err; }}
onblur={() => {
debouncedEmailCheck();
}}
/>
+3 -3
View File
@@ -28,6 +28,7 @@
import { Checkbox } from '$lib/components/ui/checkbox';
import * as Card from '$lib/components/ui/card';
import { Input } from '$lib/components/ui/input';
import { EmailInput } from '$lib/components/ui/email-input/index.js';
import { PhoneInput } from '$lib/components/ui/phone-input/index.js';
import { Separator } from '$lib/components/ui/separator';
import * as AlertDialog from '$lib/components/ui/alert-dialog';
@@ -2099,11 +2100,10 @@
<label for="recipient-email" class="text-sm font-medium text-gray-700"
>Friend's Email (Optional)</label
>
<Input
<EmailInput
id="recipient-email"
type="email"
placeholder="friend@example.com (blank to send to yourself)"
bind:value={buyRecipientEmail}
placeholder="friend@example.com (blank to send to yourself)"
class="mt-1"
/>
</div>
+8 -48
View File
@@ -2,6 +2,7 @@
import { resolve } from '$app/paths';
import { Button } from '$lib/components/ui/button/index.js';
import { Input } from '$lib/components/ui/input/index.js';
import { EmailInput } from '$lib/components/ui/email-input/index.js';
import { PhoneInput } from '$lib/components/ui/phone-input/index.js';
import { Label } from '$lib/components/ui/label/index.js';
import { Separator } from '$lib/components/ui/separator/index.js';
@@ -67,37 +68,6 @@
};
}
// Email validation
function validateEmail(email: string): boolean {
// ASCII-only email regex (safe with most providers)
const asciiRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
// Unicode-friendly regex (valid per RFC 6531)
const unicodeRegex = /^[\p{L}\p{M}0-9._%+-]+@[\p{L}\p{M}0-9.-]+\.[\p{L}\p{M}]{2,}$/u;
if (!email) {
validationErrors.email = '';
return true;
}
if (asciiRegex.test(email)) {
// OK: standard ASCII address
validationErrors.email = '';
return true;
} else if (unicodeRegex.test(email)) {
// Looks like a valid internationalised address, but unsupported
validationErrors.email =
'This looks like a valid international email address, ' +
'but our providers only supports standard (A-Z, 0-9) email addresses. ' +
'We are sorry. Please use an ASCII-compatible email.';
return false;
} else {
// Not even valid under the Unicode spec
validationErrors.email = 'Invalid email format';
return false;
}
}
function validatePhone(phone: string): boolean {
const valid = isValidUKPhone(phone);
validationErrors.phone = valid ? '' : 'Please enter a valid UK phone number';
@@ -217,7 +187,7 @@
}
// Validate before submitting
const isEmailValid = validateEmail(formData.email);
const isEmailValid = !validationErrors.email && formData.email.trim().length > 0;
const isPhoneValid = validatePhone(formData.phone);
const isAgeValid = validateAge(formData.dateOfBirth);
@@ -384,18 +354,13 @@
<div class="space-y-2">
<RequiredLabel forId="email" text="Email" />
<Input
<EmailInput
id="email"
type="email"
placeholder="john@example.com"
maxlength={255}
bind:value={formData.email}
onblur={() => validateEmail(formData.email)}
bind:error={validationErrors.email}
placeholder="john@example.com"
required
/>
{#if validationErrors.email}
<p class="text-sm text-red-500">{validationErrors.email}</p>
{/if}
</div>
<div class="space-y-2">
@@ -543,18 +508,13 @@
<div class="space-y-4">
<div class="space-y-2">
<RequiredLabel forId="email" text="Email" />
<Input
<EmailInput
id="email"
type="email"
placeholder="john@example.com"
maxlength={255}
bind:value={formData.email}
onblur={() => validateEmail(formData.email)}
bind:error={validationErrors.email}
placeholder="john@example.com"
required
/>
{#if validationErrors.email}
<p class="text-sm text-red-500">{validationErrors.email}</p>
{/if}
</div>
<div class="space-y-2">