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:
@@ -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();
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user