Meet & Greet nudge
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import Icon from '$lib/components/Icon.svelte';
|
||||
import SuccessModal from '$lib/components/SuccessModal.svelte';
|
||||
import ErrorModal from '$lib/components/ErrorModal.svelte';
|
||||
import { reveal } from '$lib/actions/reveal';
|
||||
import type { BookingContent } from '$lib/types';
|
||||
|
||||
@@ -26,7 +27,32 @@
|
||||
let errors: Record<string, string> = {};
|
||||
let submitting = false;
|
||||
let submitted = false;
|
||||
let submitError = '';
|
||||
let showErrorModal = false;
|
||||
let submitErrorDetail = '';
|
||||
|
||||
function validateEmail(raw: string): string {
|
||||
const value = raw.trim();
|
||||
if (!value) return 'Please enter your email address';
|
||||
if (!value.includes('@')) return 'Email is missing the @ sign';
|
||||
|
||||
const [local, ...domainParts] = value.split('@');
|
||||
const domain = domainParts.join('@');
|
||||
|
||||
if (domainParts.length > 1) return 'Email can only contain one @ sign';
|
||||
if (!local) return 'Please add the part before the @';
|
||||
if (!domain) return 'Please add a domain after the @, like @gmail.com';
|
||||
if (!domain.includes('.')) return 'Please include a domain ending, like @gmail.com';
|
||||
|
||||
const tld = domain.split('.').pop() ?? '';
|
||||
if (tld.length < 2) return 'That domain ending looks too short';
|
||||
|
||||
if (/\s/.test(value)) return 'Email cannot contain spaces';
|
||||
|
||||
const re = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}$/;
|
||||
if (!re.test(value)) return 'That email doesn’t look quite right';
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
const defaultDogIntro =
|
||||
'Tell us about your dog and where you are based so we can plan the right Meet & Greet.';
|
||||
@@ -70,7 +96,10 @@
|
||||
const next: Record<string, string> = {};
|
||||
|
||||
if (!fullName.trim()) next.fullName = 'Please enter your full name';
|
||||
if (!email.trim() || !emailInput?.checkValidity()) next.email = 'Please enter a valid email address';
|
||||
|
||||
const emailError = validateEmail(email);
|
||||
if (emailError) next.email = emailError;
|
||||
|
||||
if (!phone.trim()) next.phone = 'Please enter your contact number';
|
||||
|
||||
errors = next;
|
||||
@@ -108,7 +137,8 @@
|
||||
}
|
||||
|
||||
submitting = true;
|
||||
submitError = '';
|
||||
submitErrorDetail = '';
|
||||
showErrorModal = false;
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/submit', {
|
||||
@@ -124,19 +154,23 @@
|
||||
|
||||
if (!res.ok) {
|
||||
const body = await res.json().catch(() => ({}));
|
||||
throw new Error(body.detail ?? 'Something went wrong. Please try again.');
|
||||
const detail = typeof body?.detail === 'string'
|
||||
? body.detail
|
||||
: body?.detail?.message ?? body?.message ?? `Server responded with ${res.status}`;
|
||||
throw new Error(detail);
|
||||
}
|
||||
|
||||
submitted = true;
|
||||
} catch (err: unknown) {
|
||||
submitError = err instanceof Error ? err.message : 'Something went wrong. Please try again.';
|
||||
submitErrorDetail = err instanceof Error ? err.message : String(err);
|
||||
showErrorModal = true;
|
||||
} finally {
|
||||
submitting = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<section id="reservation" use:reveal={{ delay: 70 }} class="reveal-block">
|
||||
<section id="newlead" use:reveal={{ delay: 70 }} class="reveal-block">
|
||||
<div class="form-inner">
|
||||
|
||||
{#if submitted}
|
||||
@@ -148,6 +182,14 @@
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if showErrorModal}
|
||||
<ErrorModal
|
||||
detail={submitErrorDetail}
|
||||
onClose={() => (showErrorModal = false)}
|
||||
onRetry={() => (showErrorModal = false)}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<div class="booking-header">
|
||||
<h2 class="booking-title">
|
||||
<span class="booking-title-plain">{headingParts.plain}</span>{' '}<span class="booking-title-highlight">{headingParts.highlight}</span>
|
||||
@@ -228,6 +270,11 @@
|
||||
placeholder="Email"
|
||||
class:input-invalid={errors.email}
|
||||
on:input={() => clearError('email')}
|
||||
on:blur={() => {
|
||||
if (!email.trim()) return;
|
||||
const msg = validateEmail(email);
|
||||
errors = { ...errors, email: msg };
|
||||
}}
|
||||
/>
|
||||
{#if errors.email}
|
||||
<p class="field-error">
|
||||
@@ -377,13 +424,6 @@
|
||||
{#if submitting}Sending…{:else}Send <Icon name="fas fa-arrow-right" />{/if}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{#if submitError}
|
||||
<p class="booking-submit-error">
|
||||
<Icon name="fas fa-circle-exclamation" />
|
||||
{submitError}
|
||||
</p>
|
||||
{/if}
|
||||
{/if}
|
||||
</form>
|
||||
</div>
|
||||
@@ -403,13 +443,4 @@
|
||||
opacity: 1;
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
|
||||
.booking-submit-error {
|
||||
margin: 16px 0 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: #c0392b;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user