4.2.1 final fixes
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
<script lang="ts">
|
||||
import BookingSection from '$lib/components/BookingSection.svelte';
|
||||
import Icon from '$lib/components/Icon.svelte';
|
||||
import type { BookingContent } from '$lib/types';
|
||||
import InfoSection from '$lib/components/InfoSection.svelte';
|
||||
import type { BookingContent, InfoContent } from '$lib/types';
|
||||
|
||||
export let booking: BookingContent;
|
||||
export let info: InfoContent;
|
||||
export let allowGeneralEnquiry = false;
|
||||
|
||||
const email = 'info@goodwalk.co.nz';
|
||||
@@ -35,6 +37,7 @@
|
||||
</section>
|
||||
|
||||
<BookingSection {booking} {allowGeneralEnquiry} />
|
||||
<InfoSection {info} />
|
||||
</main>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -74,13 +74,13 @@
|
||||
enquiryType = 'booking';
|
||||
}
|
||||
$: isGeneralEnquiry = allowGeneralEnquiry && enquiryType === 'general';
|
||||
$: ownerSubtitle = isGeneralEnquiry
|
||||
$: ownerIntro = isGeneralEnquiry
|
||||
? booking.generalSubtitle?.trim() || defaultGeneralSubtitle
|
||||
: booking.subtitle;
|
||||
$: ownerStepLabel = booking.ownerStepLabel?.trim() || 'Owner Details';
|
||||
$: dogStepLabel = booking.dogStepLabel?.trim() || 'Your dog';
|
||||
$: firstStepLabel = isGeneralEnquiry ? 'Your enquiry' : dogStepLabel;
|
||||
$: firstStepIntro = isGeneralEnquiry ? generalIntro : dogIntro;
|
||||
$: detailsStepLabel = isGeneralEnquiry ? 'Your enquiry' : dogStepLabel;
|
||||
$: detailsStepIntro = isGeneralEnquiry ? generalIntro : dogIntro;
|
||||
$: successPetName = petName.trim() || 'your dog';
|
||||
|
||||
onMount(() => {
|
||||
@@ -126,7 +126,37 @@
|
||||
errors = {};
|
||||
}
|
||||
|
||||
function validateFirstStep(): boolean {
|
||||
function validateOwnerStep(): boolean {
|
||||
const next: Record<string, string> = {};
|
||||
|
||||
if (!fullName.trim()) next.fullName = 'Please enter your full name';
|
||||
|
||||
const emailError = validateEmail(email);
|
||||
if (emailError) next.email = emailError;
|
||||
|
||||
if (!phone.trim()) next.phone = 'Please enter your contact number';
|
||||
|
||||
errors = next;
|
||||
|
||||
if (next.fullName) {
|
||||
fullNameInput?.focus();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (next.email) {
|
||||
emailInput?.focus();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (next.phone) {
|
||||
phoneInput?.focus();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function validateDetailsStep(): boolean {
|
||||
const next: Record<string, string> = {};
|
||||
|
||||
if (isGeneralEnquiry) {
|
||||
@@ -156,7 +186,7 @@
|
||||
}
|
||||
|
||||
function goToOwnerStep() {
|
||||
if (!validateFirstStep()) return;
|
||||
if (!validateDetailsStep()) return;
|
||||
errors = {};
|
||||
step = 2;
|
||||
}
|
||||
@@ -169,22 +199,11 @@
|
||||
return;
|
||||
}
|
||||
|
||||
const next: Record<string, string> = {};
|
||||
if (!fullName.trim()) next.fullName = 'Please enter your full name';
|
||||
|
||||
const emailError = validateEmail(email);
|
||||
if (emailError) next.email = emailError;
|
||||
|
||||
if (!phone.trim()) next.phone = 'Please enter your contact number';
|
||||
|
||||
if (Object.keys(next).length > 0) {
|
||||
errors = next;
|
||||
if (next.fullName) fullNameInput?.focus();
|
||||
else if (next.email) emailInput?.focus();
|
||||
else if (next.phone) phoneInput?.focus();
|
||||
if (!validateOwnerStep()) {
|
||||
return;
|
||||
}
|
||||
|
||||
errors = {};
|
||||
submitting = true;
|
||||
submitErrorDetail = '';
|
||||
showErrorModal = false;
|
||||
@@ -194,26 +213,27 @@
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
enquiryType,
|
||||
fullName,
|
||||
email,
|
||||
phone,
|
||||
petName: isGeneralEnquiry ? '' : petName,
|
||||
location: isGeneralEnquiry ? '' : location,
|
||||
message,
|
||||
services: isGeneralEnquiry ? [] : selectedServices,
|
||||
website,
|
||||
formStartedAt,
|
||||
referrer: document.referrer,
|
||||
page: window.location.href,
|
||||
})
|
||||
enquiryType,
|
||||
fullName,
|
||||
email,
|
||||
phone,
|
||||
petName: isGeneralEnquiry ? '' : petName,
|
||||
location: isGeneralEnquiry ? '' : location,
|
||||
message,
|
||||
services: isGeneralEnquiry ? [] : selectedServices,
|
||||
website,
|
||||
formStartedAt,
|
||||
referrer: document.referrer,
|
||||
page: window.location.href
|
||||
})
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const body = await res.json().catch(() => ({}));
|
||||
const detail = typeof body?.detail === 'string'
|
||||
? body.detail
|
||||
: body?.detail?.message ?? body?.message ?? `Server responded with ${res.status}`;
|
||||
const detail =
|
||||
typeof body?.detail === 'string'
|
||||
? body.detail
|
||||
: body?.detail?.message ?? body?.message ?? `Server responded with ${res.status}`;
|
||||
throw new Error(detail);
|
||||
}
|
||||
|
||||
@@ -229,7 +249,6 @@
|
||||
|
||||
<section id="newlead" use:reveal={{ delay: 70 }} class="reveal-block">
|
||||
<div class="form-inner">
|
||||
|
||||
{#if submitted}
|
||||
<SuccessModal
|
||||
firstName={fullName.split(' ')[0]}
|
||||
@@ -251,7 +270,8 @@
|
||||
|
||||
<div class="booking-header">
|
||||
<h2 class="booking-title">
|
||||
<span class="booking-title-plain">{headingParts.plain}</span>{' '}<span class="booking-title-highlight">{headingParts.highlight}</span>
|
||||
<span class="booking-title-plain">{headingParts.plain}</span>{' '}
|
||||
<span class="booking-title-highlight">{headingParts.highlight}</span>
|
||||
</h2>
|
||||
|
||||
<div class="booking-stepper" aria-label="Booking form steps">
|
||||
@@ -259,10 +279,13 @@
|
||||
type="button"
|
||||
class:active={step === 1}
|
||||
class="booking-step"
|
||||
on:click={() => (step = 1)}
|
||||
on:click={() => {
|
||||
step = 1;
|
||||
errors = {};
|
||||
}}
|
||||
>
|
||||
<span class="booking-step-number">1</span>
|
||||
<span class="booking-step-label">{firstStepLabel}</span>
|
||||
<span class="booking-step-label">{detailsStepLabel}</span>
|
||||
</button>
|
||||
<span class="booking-step-divider" aria-hidden="true"></span>
|
||||
<button
|
||||
@@ -277,12 +300,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form
|
||||
class="booking-form"
|
||||
id="bookingForm"
|
||||
novalidate
|
||||
on:submit={handleSubmit}
|
||||
>
|
||||
<form class="booking-form" id="bookingForm" novalidate on:submit={handleSubmit}>
|
||||
<div class="booking-honeypot" aria-hidden="true">
|
||||
<label for="website">Website</label>
|
||||
<input
|
||||
@@ -296,12 +314,16 @@
|
||||
</div>
|
||||
|
||||
{#if step === 1}
|
||||
<input type="hidden" name="enquiryType" value={enquiryType} />
|
||||
<div class="booking-panel">
|
||||
{#if firstStepIntro}
|
||||
<div class="booking-panel-banner">{firstStepIntro}</div>
|
||||
{#if detailsStepIntro}
|
||||
<div class="booking-panel-banner">{detailsStepIntro}</div>
|
||||
{/if}
|
||||
|
||||
<div class:booking-card-grid-with-banner={Boolean(firstStepIntro)} class="booking-card-grid booking-card-grid-dog">
|
||||
<div
|
||||
class:booking-card-grid-with-banner={Boolean(detailsStepIntro)}
|
||||
class="booking-card-grid booking-card-grid-dog"
|
||||
>
|
||||
{#if allowGeneralEnquiry}
|
||||
<div class="booking-field-card booking-field-card-full">
|
||||
<label>
|
||||
@@ -361,7 +383,10 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="booking-field-card booking-field-card-wide" class:booking-field-card-invalid={errors.location}>
|
||||
<div
|
||||
class="booking-field-card booking-field-card-wide"
|
||||
class:booking-field-card-invalid={errors.location}
|
||||
>
|
||||
<label for="location">
|
||||
<Icon name="fas fa-location-dot" /> Location <span class="booking-required">*</span>
|
||||
</label>
|
||||
@@ -385,7 +410,10 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="booking-field-card booking-field-card-full" class:booking-field-card-invalid={errors.message}>
|
||||
<div
|
||||
class="booking-field-card booking-field-card-full"
|
||||
class:booking-field-card-invalid={errors.message}
|
||||
>
|
||||
<label for="message">
|
||||
<Icon name="fas fa-comment" /> {isGeneralEnquiry ? 'Your Message' : 'About Your Dog'}
|
||||
{#if isGeneralEnquiry}<span class="booking-required">*</span>{/if}
|
||||
@@ -438,26 +466,25 @@
|
||||
{ownerStepLabel}
|
||||
<Icon name="fas fa-arrow-right" />
|
||||
</button>
|
||||
<p class="booking-next-note">Response from us within 24 hours</p>
|
||||
</div>
|
||||
{:else}
|
||||
<input type="hidden" name="enquiryType" value={enquiryType} />
|
||||
{#if !isGeneralEnquiry}
|
||||
<input type="hidden" name="petName" value={petName} />
|
||||
<input type="hidden" name="location" value={location} />
|
||||
{/if}
|
||||
<input type="hidden" name="fullName" value={fullName} />
|
||||
<input type="hidden" name="email" value={email} />
|
||||
<input type="hidden" name="phone" value={phone} />
|
||||
<input type="hidden" name="petName" value={petName} />
|
||||
<input type="hidden" name="location" value={location} />
|
||||
<input type="hidden" name="message" value={message} />
|
||||
{#if !isGeneralEnquiry}
|
||||
{#each selectedServices as service}
|
||||
<input type="hidden" name="services" value={service} />
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
<div class="booking-panel">
|
||||
{#if ownerSubtitle}
|
||||
<div class="booking-panel-banner">{ownerSubtitle}</div>
|
||||
{#if ownerIntro}
|
||||
<div class="booking-panel-banner">{ownerIntro}</div>
|
||||
{/if}
|
||||
|
||||
<div class:booking-card-grid-with-banner={Boolean(ownerSubtitle)} class="booking-card-grid booking-card-grid-owner">
|
||||
<div
|
||||
class:booking-card-grid-with-banner={Boolean(ownerIntro)}
|
||||
class="booking-card-grid booking-card-grid-owner"
|
||||
>
|
||||
<div class="booking-field-card booking-field-card-group booking-field-card-full">
|
||||
<div class="booking-field-group booking-field-group-owner">
|
||||
<div class="booking-field-stack" class:booking-field-stack-invalid={errors.fullName}>
|
||||
@@ -542,7 +569,10 @@
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline btn-outline-green"
|
||||
on:click={() => { step = 1; errors = {}; }}
|
||||
on:click={() => {
|
||||
step = 1;
|
||||
errors = {};
|
||||
}}
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
|
||||
@@ -3,6 +3,37 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import BookingSection from './BookingSection.svelte';
|
||||
import { homepageContent } from '$lib/content/homepage';
|
||||
|
||||
async function fillOwnerStep() {
|
||||
await fireEvent.input(screen.getByLabelText(/Full Name/i), {
|
||||
target: { value: 'Alex Walker' }
|
||||
});
|
||||
await fireEvent.input(screen.getByLabelText(/^Email/i), {
|
||||
target: { value: 'alex@example.com' }
|
||||
});
|
||||
await fireEvent.input(screen.getByLabelText(/Contact #/i), {
|
||||
target: { value: '021 123 4567' }
|
||||
});
|
||||
}
|
||||
|
||||
async function fillDogStep() {
|
||||
await fireEvent.click(screen.getByLabelText('Pack Walks'));
|
||||
await fireEvent.click(screen.getByLabelText('Other Services'));
|
||||
await fireEvent.input(screen.getByLabelText(/Dog's Name/i), {
|
||||
target: { value: 'Maya' }
|
||||
});
|
||||
await fireEvent.input(screen.getByLabelText(/Location/i), {
|
||||
target: { value: 'Kingsland' }
|
||||
});
|
||||
await fireEvent.input(screen.getByLabelText(/About Your Dog/i), {
|
||||
target: { value: 'Loves small group walks.' }
|
||||
});
|
||||
}
|
||||
|
||||
async function moveToOwnerStep(container: HTMLElement) {
|
||||
await fillDogStep();
|
||||
await fireEvent.click(container.querySelector('.booking-next-button')!);
|
||||
}
|
||||
|
||||
describe('BookingSection', () => {
|
||||
beforeEach(() => {
|
||||
Object.defineProperty(document, 'referrer', {
|
||||
@@ -29,14 +60,7 @@ describe('BookingSection', () => {
|
||||
booking: homepageContent.booking
|
||||
});
|
||||
|
||||
await fireEvent.input(screen.getByLabelText(/Dog's Name/i), {
|
||||
target: { value: 'Maya' }
|
||||
});
|
||||
await fireEvent.input(screen.getByLabelText(/Location/i), {
|
||||
target: { value: 'Kingsland' }
|
||||
});
|
||||
|
||||
await fireEvent.click(container.querySelector('.booking-next-button')!);
|
||||
await moveToOwnerStep(container);
|
||||
await fireEvent.click(container.querySelector('.booking-submit-button')!);
|
||||
|
||||
expect(screen.getByText('Please enter your full name')).toBeInTheDocument();
|
||||
@@ -55,30 +79,8 @@ describe('BookingSection', () => {
|
||||
booking: homepageContent.booking
|
||||
});
|
||||
|
||||
await fireEvent.click(screen.getByLabelText('Pack Walks'));
|
||||
await fireEvent.click(screen.getByLabelText('Other Services'));
|
||||
|
||||
await fireEvent.input(screen.getByLabelText(/Dog's Name/i), {
|
||||
target: { value: 'Maya' }
|
||||
});
|
||||
await fireEvent.input(screen.getByLabelText(/Location/i), {
|
||||
target: { value: 'Kingsland' }
|
||||
});
|
||||
await fireEvent.input(screen.getByLabelText(/About Your Dog/i), {
|
||||
target: { value: 'Loves small group walks.' }
|
||||
});
|
||||
|
||||
await fireEvent.click(container.querySelector('.booking-next-button')!);
|
||||
|
||||
await fireEvent.input(screen.getByLabelText(/Full Name/i), {
|
||||
target: { value: 'Alex Walker' }
|
||||
});
|
||||
await fireEvent.input(screen.getByLabelText(/^Email/i), {
|
||||
target: { value: 'alex@example.com' }
|
||||
});
|
||||
await fireEvent.input(screen.getByLabelText(/Contact #/i), {
|
||||
target: { value: '021 123 4567' }
|
||||
});
|
||||
await moveToOwnerStep(container);
|
||||
await fillOwnerStep();
|
||||
|
||||
await fireEvent.click(container.querySelector('.booking-submit-button')!);
|
||||
|
||||
@@ -128,15 +130,7 @@ describe('BookingSection', () => {
|
||||
allowGeneralEnquiry: true
|
||||
});
|
||||
|
||||
await fireEvent.input(screen.getByLabelText(/Dog's Name/i), {
|
||||
target: { value: 'Maya' }
|
||||
});
|
||||
await fireEvent.input(screen.getByLabelText(/Location/i), {
|
||||
target: { value: 'Grey Lynn' }
|
||||
});
|
||||
await fireEvent.click(screen.getByLabelText('Pack Walks'));
|
||||
await fireEvent.click(screen.getByLabelText(/General enquiry/i));
|
||||
|
||||
expect(screen.queryByLabelText(/Dog's Name/i)).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('Pack Walks')).not.toBeInTheDocument();
|
||||
|
||||
@@ -148,17 +142,7 @@ describe('BookingSection', () => {
|
||||
});
|
||||
|
||||
await fireEvent.click(container.querySelector('.booking-next-button')!);
|
||||
|
||||
await fireEvent.input(screen.getByLabelText(/Full Name/i), {
|
||||
target: { value: 'Alex Walker' }
|
||||
});
|
||||
await fireEvent.input(screen.getByLabelText(/^Email/i), {
|
||||
target: { value: 'alex@example.com' }
|
||||
});
|
||||
await fireEvent.input(screen.getByLabelText(/Contact #/i), {
|
||||
target: { value: '021 123 4567' }
|
||||
});
|
||||
|
||||
await fillOwnerStep();
|
||||
await fireEvent.click(container.querySelector('.booking-submit-button')!);
|
||||
|
||||
await waitFor(() => expect(fetchMock).toHaveBeenCalledTimes(1));
|
||||
@@ -192,23 +176,8 @@ describe('BookingSection', () => {
|
||||
booking: homepageContent.booking
|
||||
});
|
||||
|
||||
await fireEvent.input(screen.getByLabelText(/Dog's Name/i), {
|
||||
target: { value: 'Maya' }
|
||||
});
|
||||
await fireEvent.input(screen.getByLabelText(/Location/i), {
|
||||
target: { value: 'Kingsland' }
|
||||
});
|
||||
await fireEvent.click(container.querySelector('.booking-next-button')!);
|
||||
|
||||
await fireEvent.input(screen.getByLabelText(/Full Name/i), {
|
||||
target: { value: 'Alex Walker' }
|
||||
});
|
||||
await fireEvent.input(screen.getByLabelText(/^Email/i), {
|
||||
target: { value: 'alex@example.com' }
|
||||
});
|
||||
await fireEvent.input(screen.getByLabelText(/Contact #/i), {
|
||||
target: { value: '021 123 4567' }
|
||||
});
|
||||
await moveToOwnerStep(container);
|
||||
await fillOwnerStep();
|
||||
|
||||
await fireEvent.click(container.querySelector('.booking-submit-button')!);
|
||||
|
||||
|
||||
@@ -85,7 +85,13 @@
|
||||
rel="noopener"
|
||||
class="footer-reviews"
|
||||
>
|
||||
<Icon name="fab fa-google" />
|
||||
<img
|
||||
class="footer-google-logo"
|
||||
src="/images/google-g-logo.svg"
|
||||
alt=""
|
||||
width="16"
|
||||
height="17"
|
||||
/>
|
||||
<span>30+ five-star Google reviews</span>
|
||||
</a>
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
<script lang="ts">
|
||||
import Icon from '$lib/components/Icon.svelte';
|
||||
import { getImageMetadata } from '$lib/image-metadata';
|
||||
import type { HeroContent } from '$lib/types';
|
||||
import type { CallToAction, HeroContent } from '$lib/types';
|
||||
|
||||
export let hero: HeroContent;
|
||||
export let reviewCta: CallToAction | undefined = undefined;
|
||||
|
||||
$: titleParts = splitTitle(hero.title);
|
||||
$: mobileTitle = hero.mobileTitle?.trim() || `${hero.title} ${hero.highlight}`.trim();
|
||||
@@ -44,6 +46,30 @@
|
||||
<p class="hero-subtitle">{hero.subtitle}</p>
|
||||
{/if}
|
||||
|
||||
{#if reviewCta}
|
||||
<a
|
||||
class="hero-trust-chip"
|
||||
href={reviewCta.href}
|
||||
target={reviewCta.external ? '_blank' : undefined}
|
||||
rel={reviewCta.external ? 'noopener' : undefined}
|
||||
aria-label="Read our five-star Google reviews"
|
||||
>
|
||||
<img
|
||||
class="hero-trust-logo"
|
||||
src="/images/google-g-logo.svg"
|
||||
alt=""
|
||||
width="18"
|
||||
height="19"
|
||||
/>
|
||||
<span class="hero-trust-stars" aria-hidden="true">
|
||||
{#each Array(5) as _}
|
||||
<Icon name="fas fa-star" />
|
||||
{/each}
|
||||
</span>
|
||||
<span>{reviewCta.label}</span>
|
||||
</a>
|
||||
{/if}
|
||||
|
||||
<div class="hero-buttons">
|
||||
<a href={hero.primaryCta.href} class="btn btn-yellow">{hero.primaryCta.label}</a>
|
||||
<a href={hero.secondaryCta.href} class="btn btn-outline">{hero.secondaryCta.label}</a>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { onMount, tick } from 'svelte';
|
||||
import { afterNavigate } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import Icon from '$lib/components/Icon.svelte';
|
||||
@@ -23,32 +23,78 @@
|
||||
$: pathname = $page.url.pathname;
|
||||
$: hidden = pathname === '/contact-us' || pathname === '/booking';
|
||||
|
||||
const SHOW_AFTER_PX = 480; // close to one mobile viewport
|
||||
const HIDE_BELOW_PX = 120; // generous so the bar doesn't flicker near the top
|
||||
|
||||
let visible = false;
|
||||
let triggerPassed = false;
|
||||
let bookingInView = false;
|
||||
let triggerObserver: IntersectionObserver | null = null;
|
||||
let bookingObserver: IntersectionObserver | null = null;
|
||||
|
||||
function evaluateVisibility() {
|
||||
const y = window.scrollY;
|
||||
if (y > SHOW_AFTER_PX) {
|
||||
visible = true;
|
||||
} else if (y < HIDE_BELOW_PX) {
|
||||
visible = false;
|
||||
function refreshVisibility() {
|
||||
visible = !hidden && triggerPassed && !bookingInView;
|
||||
}
|
||||
|
||||
function cleanupObservers() {
|
||||
triggerObserver?.disconnect();
|
||||
bookingObserver?.disconnect();
|
||||
triggerObserver = null;
|
||||
bookingObserver = null;
|
||||
}
|
||||
|
||||
async function setupObservers() {
|
||||
if (typeof window === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
await tick();
|
||||
|
||||
cleanupObservers();
|
||||
|
||||
const triggerEl =
|
||||
document.getElementById('hero') ?? document.querySelector('main section, section');
|
||||
const bookingEl = document.getElementById('newlead');
|
||||
|
||||
triggerPassed = !triggerEl;
|
||||
bookingInView = false;
|
||||
|
||||
if (triggerEl) {
|
||||
triggerObserver = new IntersectionObserver(
|
||||
([entry]) => {
|
||||
triggerPassed = !entry.isIntersecting && entry.boundingClientRect.top < 0;
|
||||
refreshVisibility();
|
||||
},
|
||||
{ threshold: 0.2 }
|
||||
);
|
||||
|
||||
triggerObserver.observe(triggerEl);
|
||||
}
|
||||
|
||||
if (bookingEl) {
|
||||
bookingObserver = new IntersectionObserver(
|
||||
([entry]) => {
|
||||
bookingInView = entry.isIntersecting;
|
||||
refreshVisibility();
|
||||
},
|
||||
{ threshold: 0.2 }
|
||||
);
|
||||
|
||||
bookingObserver.observe(bookingEl);
|
||||
}
|
||||
|
||||
refreshVisibility();
|
||||
}
|
||||
|
||||
afterNavigate(() => {
|
||||
visible = false;
|
||||
triggerPassed = false;
|
||||
bookingInView = false;
|
||||
void setupObservers();
|
||||
});
|
||||
|
||||
onMount(() => {
|
||||
evaluateVisibility();
|
||||
window.addEventListener('scroll', evaluateVisibility, { passive: true });
|
||||
window.addEventListener('resize', evaluateVisibility, { passive: true });
|
||||
void setupObservers();
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('scroll', evaluateVisibility);
|
||||
window.removeEventListener('resize', evaluateVisibility);
|
||||
cleanupObservers();
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -104,18 +104,25 @@
|
||||
rel="noopener"
|
||||
aria-label="Read our 5-star Google reviews"
|
||||
>
|
||||
<img
|
||||
class="pricing-trust-logo"
|
||||
src="/images/google-g-logo.svg"
|
||||
alt=""
|
||||
width="18"
|
||||
height="19"
|
||||
/>
|
||||
<span class="pricing-trust-stars" aria-hidden="true">
|
||||
{#each Array(5) as _}
|
||||
<Icon name="fas fa-star" />
|
||||
{/each}
|
||||
</span>
|
||||
<span class="pricing-trust-label">30+ five-star Google reviews</span>
|
||||
<span class="pricing-trust-label">30+ 5-star Google reviews, trusted by Auckland dog owners</span>
|
||||
<Icon name="fas fa-arrow-right" className="pricing-trust-arrow" />
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{#each pageContent.sections as section}
|
||||
{#each pageContent.sections as section, index}
|
||||
<section use:reveal class="pricing-section reveal-block">
|
||||
<div class="pricing-inner">
|
||||
<div class="pricing-section-heading">
|
||||
@@ -133,7 +140,8 @@
|
||||
class={`btn pricing-section-link ${section.detailCta.variant === 'yellow' ? 'btn-yellow' : section.detailCta.variant === 'outline' ? 'btn-outline' : 'btn-green'}`}
|
||||
href={section.detailCta.href}
|
||||
>
|
||||
{section.detailCta.label}
|
||||
<span>{section.detailCta.label}</span>
|
||||
<Icon name="fas fa-arrow-right" />
|
||||
</a>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -159,6 +167,25 @@
|
||||
</article>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<a class="btn btn-yellow pricing-section-mobile-cta" href="#newlead">
|
||||
Book a Meet & Greet
|
||||
</a>
|
||||
|
||||
{#if index === 0}
|
||||
<aside class="pricing-mobile-consult" aria-label="Need help choosing the right option?">
|
||||
<span class="pricing-mobile-consult-kicker">
|
||||
<Icon name="fas fa-comment-dots" />
|
||||
Not sure which option fits?
|
||||
</span>
|
||||
<p>
|
||||
Book a free Meet & Greet and we’ll help you choose the right walk or visit for your dog.
|
||||
</p>
|
||||
<a class="btn btn-outline btn-outline-green pricing-mobile-consult-cta" href="#newlead">
|
||||
Talk it through with us
|
||||
</a>
|
||||
</aside>
|
||||
{/if}
|
||||
</div>
|
||||
</section>
|
||||
{/each}
|
||||
@@ -238,6 +265,12 @@
|
||||
transform 0.18s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
.pricing-trust-logo {
|
||||
width: 18px;
|
||||
height: 19px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.pricing-trust:hover {
|
||||
background: rgba(255, 255, 255, 0.18);
|
||||
transform: translateY(-1px);
|
||||
@@ -307,6 +340,9 @@
|
||||
}
|
||||
|
||||
.pricing-section-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-top: 18px;
|
||||
}
|
||||
|
||||
@@ -395,6 +431,11 @@
|
||||
font-family: var(--font-head);
|
||||
}
|
||||
|
||||
.pricing-section-mobile-cta,
|
||||
.pricing-mobile-consult {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.meet-greet-prompt {
|
||||
position: fixed;
|
||||
right: 24px;
|
||||
@@ -559,6 +600,12 @@
|
||||
font-size: 34px;
|
||||
}
|
||||
|
||||
.pricing-trust {
|
||||
gap: 10px;
|
||||
padding: 10px 14px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.pricing-section-heading h2 {
|
||||
font-size: 26px;
|
||||
}
|
||||
@@ -568,7 +615,7 @@
|
||||
}
|
||||
|
||||
.pricing-section-heading {
|
||||
margin-bottom: 20px;
|
||||
margin-bottom: 26px;
|
||||
}
|
||||
|
||||
.pricing-section-blurb {
|
||||
@@ -576,6 +623,11 @@
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.pricing-section-link {
|
||||
margin-top: 22px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.pricing-plan-grid,
|
||||
.pricing-plan-grid-three {
|
||||
grid-template-columns: 1fr;
|
||||
@@ -594,6 +646,51 @@
|
||||
font-size: 46px;
|
||||
}
|
||||
|
||||
.pricing-plan-cta {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.pricing-section-mobile-cta {
|
||||
display: flex;
|
||||
width: fit-content;
|
||||
margin: 18px auto 0;
|
||||
font-family: var(--font-head);
|
||||
}
|
||||
|
||||
.pricing-mobile-consult {
|
||||
display: block;
|
||||
margin-top: 18px;
|
||||
padding: 22px 20px;
|
||||
border-radius: 24px;
|
||||
background: linear-gradient(180deg, #fffaf0 0%, #f9f4e7 100%);
|
||||
box-shadow: 0 12px 28px rgba(17, 20, 24, 0.05);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.pricing-mobile-consult-kicker {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 10px;
|
||||
color: var(--green);
|
||||
font-family: var(--font-head);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.pricing-mobile-consult p {
|
||||
margin: 0;
|
||||
color: #34363a;
|
||||
font-size: 15px;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.pricing-mobile-consult-cta {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.meet-greet-prompt {
|
||||
right: 16px;
|
||||
left: 16px;
|
||||
|
||||
@@ -125,6 +125,8 @@
|
||||
Every booking starts with a free, no-obligation Meet & Greet.
|
||||
</p>
|
||||
|
||||
<a class="btn btn-yellow service-plan-mobile-cta" href="#newlead">Book a Meet & Greet</a>
|
||||
|
||||
{#if pageContent.pricing.extras?.length}
|
||||
<div class="service-extras">
|
||||
<div class="service-extras-heading">Extras</div>
|
||||
@@ -566,6 +568,10 @@
|
||||
font-family: var(--font-head);
|
||||
}
|
||||
|
||||
.service-plan-mobile-cta {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.service-plan-reassurance,
|
||||
.service-plan-scarcity {
|
||||
display: flex;
|
||||
@@ -731,5 +737,16 @@
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.service-plan-cta {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.service-plan-mobile-cta {
|
||||
display: flex;
|
||||
width: fit-content;
|
||||
margin: 18px auto 0;
|
||||
font-family: var(--font-head);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -27,7 +27,8 @@
|
||||
|
||||
{#if service.href}
|
||||
<a href={service.href} class="btn btn-green">
|
||||
See {service.title} pricing →
|
||||
<span>See {service.title} pricing</span>
|
||||
<Icon name="fas fa-arrow-right" />
|
||||
</a>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -127,10 +127,6 @@
|
||||
<h2 class="section-heading">{heading}</h2>
|
||||
<div class="testimonials-intro">
|
||||
<p>{blurb}</p>
|
||||
<a href={instagramHref} target="_blank" rel="noopener" class="testimonials-instagram-link">
|
||||
<Icon name="fab fa-instagram" />
|
||||
<span>{instagramLabel}</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{#if slides.length}
|
||||
@@ -190,13 +186,39 @@
|
||||
|
||||
<div class="testimonial-divider"></div>
|
||||
|
||||
<div class="testimonial-mobile-controls" aria-label="Testimonial navigation">
|
||||
<button
|
||||
class="testimonial-arrow testimonial-arrow-inline"
|
||||
type="button"
|
||||
aria-label="Previous testimonial"
|
||||
on:click={showPrevious}
|
||||
>
|
||||
<Icon name="fas fa-chevron-left" />
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="testimonial-arrow testimonial-arrow-inline"
|
||||
type="button"
|
||||
aria-label="Next testimonial"
|
||||
on:click={showNext}
|
||||
>
|
||||
<Icon name="fas fa-chevron-right" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<a
|
||||
class="testimonial-google"
|
||||
href="https://g.page/r/CUsvrWPhkYrAEB0/"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
<Icon name="fab fa-google" />
|
||||
<img
|
||||
class="testimonial-google-logo"
|
||||
src="/images/google-g-logo.svg"
|
||||
alt=""
|
||||
width="18"
|
||||
height="19"
|
||||
/>
|
||||
<span>30+ five-star Google reviews</span>
|
||||
</a>
|
||||
</div>
|
||||
@@ -214,6 +236,11 @@
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<a href={instagramHref} target="_blank" rel="noopener" class="testimonials-instagram-link">
|
||||
<Icon name="fab fa-instagram" />
|
||||
<span>{instagramLabel}</span>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -232,10 +259,11 @@
|
||||
}
|
||||
|
||||
.testimonials-instagram-link {
|
||||
display: inline-flex;
|
||||
display: flex;
|
||||
width: fit-content;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-top: 18px;
|
||||
margin: 18px auto 0;
|
||||
padding: 10px 16px;
|
||||
border-radius: 999px;
|
||||
background: rgba(33, 48, 33, 0.06);
|
||||
@@ -283,7 +311,7 @@
|
||||
}
|
||||
|
||||
.testimonials-instagram-link {
|
||||
margin-top: 14px;
|
||||
margin: 14px auto 0;
|
||||
padding: 9px 14px;
|
||||
font-size: 15px;
|
||||
}
|
||||
@@ -443,14 +471,20 @@
|
||||
box-shadow: 0 0 0 1px rgba(10, 48, 78, 0.06);
|
||||
}
|
||||
|
||||
.testimonial-google :global(.icon) {
|
||||
font-size: 20px;
|
||||
.testimonial-google-logo {
|
||||
width: 18px;
|
||||
height: 19px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.testimonial-google:hover {
|
||||
background: #efe6d5;
|
||||
}
|
||||
|
||||
.testimonial-mobile-controls {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.testimonial-woof {
|
||||
position: absolute;
|
||||
top: 40px;
|
||||
@@ -559,7 +593,7 @@
|
||||
|
||||
.testimonial-stage {
|
||||
min-height: unset;
|
||||
padding-bottom: 116px;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.testimonial-slide {
|
||||
@@ -605,8 +639,24 @@
|
||||
margin-top: 28px;
|
||||
}
|
||||
|
||||
.testimonial-mobile-controls {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.testimonial-arrow-inline {
|
||||
position: static;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
font-size: 18px;
|
||||
transform: none;
|
||||
box-shadow: 0 10px 22px rgba(20, 24, 20, 0.08);
|
||||
}
|
||||
|
||||
.testimonial-google {
|
||||
margin-top: 28px;
|
||||
margin-top: 20px;
|
||||
font-size: 16px;
|
||||
gap: 10px;
|
||||
padding: 10px 14px;
|
||||
@@ -649,21 +699,9 @@
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.testimonial-arrow {
|
||||
top: auto;
|
||||
bottom: 24px;
|
||||
width: 54px;
|
||||
height: 54px;
|
||||
font-size: 20px;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.testimonial-arrow-left {
|
||||
left: 20px;
|
||||
}
|
||||
|
||||
.testimonial-arrow-left,
|
||||
.testimonial-arrow-right {
|
||||
right: 20px;
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -35,8 +35,8 @@ export const homepageContent: HomePageContent = {
|
||||
highlight: "Your Dog's Day!",
|
||||
mobileTitle: "Unleashing Fun in\nYour Dog's Day!",
|
||||
subtitle: 'Trusted, on-time dog walking across Auckland Central — pack walks, 1:1 walks, and puppy visits.',
|
||||
primaryCta: { label: 'Explore our services →', href: '#services', variant: 'yellow' },
|
||||
secondaryCta: { label: 'Book a Meet & Greet', href: '#newlead', variant: 'outline' },
|
||||
primaryCta: { label: 'Book a Meet & Greet', href: '#newlead', variant: 'yellow' },
|
||||
secondaryCta: { label: 'Explore our services →', href: '#services', variant: 'outline' },
|
||||
imageUrl: '/images/auckland-dog-walking-happy-dog-hero.png',
|
||||
imageAlt: 'Happy dog ready for a professional pack walk with Goodwalk Auckland dog walking service'
|
||||
},
|
||||
|
||||
@@ -76,6 +76,7 @@ describe('content server helpers', () => {
|
||||
services: homepageContent.services,
|
||||
testimonials: homepageContent.testimonials,
|
||||
booking: homepageContent.booking,
|
||||
info: homepageContent.info,
|
||||
footer: homepageContent.footer
|
||||
});
|
||||
});
|
||||
@@ -89,6 +90,7 @@ describe('content server helpers', () => {
|
||||
expect(result.services).not.toBe(homepageContent.services);
|
||||
expect(result.testimonials).not.toBe(homepageContent.testimonials);
|
||||
expect(result.booking).not.toBe(homepageContent.booking);
|
||||
expect(result.info).not.toBe(homepageContent.info);
|
||||
expect(result.footer).not.toBe(homepageContent.footer);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -55,6 +55,7 @@ export async function getSharedPageContent(): Promise<SiteSharedContent> {
|
||||
services: content.services,
|
||||
testimonials: content.testimonials,
|
||||
booking: content.booking,
|
||||
info: content.info,
|
||||
footer: content.footer
|
||||
};
|
||||
}
|
||||
|
||||
@@ -367,12 +367,21 @@
|
||||
|
||||
.booking-actions-next {
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.booking-actions-final {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.booking-next-note {
|
||||
margin: 10px 0 0;
|
||||
text-align: center;
|
||||
font-size: 13px;
|
||||
line-height: 1.45;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.booking-next-button,
|
||||
.booking-submit-button {
|
||||
display: inline-flex;
|
||||
|
||||
@@ -195,7 +195,7 @@
|
||||
|
||||
.hero-inner {
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
gap: 18px;
|
||||
align-items: stretch;
|
||||
text-align: left;
|
||||
padding: 0;
|
||||
@@ -222,6 +222,12 @@
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.hero-trust-chip {
|
||||
margin-bottom: 18px;
|
||||
padding: 10px 14px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.hero-heading-desktop {
|
||||
display: none;
|
||||
}
|
||||
@@ -239,37 +245,38 @@
|
||||
|
||||
.hero-buttons {
|
||||
width: 100%;
|
||||
justify-content: flex-start;
|
||||
gap: 10px;
|
||||
padding-right: 18px;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.hero-buttons .btn {
|
||||
flex: 0 0 auto;
|
||||
width: auto;
|
||||
flex: 1 1 0;
|
||||
width: 0;
|
||||
min-width: 0;
|
||||
padding: 17px 28px;
|
||||
font-size: 15px;
|
||||
padding: 15px 12px;
|
||||
font-size: 13.5px;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
border-radius: 999px;
|
||||
line-height: 1.25;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
touch-action: manipulation;
|
||||
}
|
||||
|
||||
.hero-buttons .btn:last-child {
|
||||
margin-right: 12px;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.hero-buttons .btn-yellow {
|
||||
background: #e8dbc1;
|
||||
background: var(--yellow);
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.hero-buttons .btn-outline {
|
||||
background: var(--yellow);
|
||||
color: #000;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: #fff;
|
||||
border: 2px solid rgba(255, 255, 255, 0.84);
|
||||
}
|
||||
|
||||
.hero-buttons .btn:active {
|
||||
@@ -277,11 +284,11 @@
|
||||
}
|
||||
|
||||
.hero-buttons .btn-yellow:active {
|
||||
background: #dccdb1;
|
||||
background: #e6bb00;
|
||||
}
|
||||
|
||||
.hero-buttons .btn-outline:active {
|
||||
background: #e6bb00;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.hero-img {
|
||||
@@ -295,7 +302,7 @@
|
||||
}
|
||||
|
||||
.hero-img img {
|
||||
width: min(100%, 500px);
|
||||
width: min(100%, 460px);
|
||||
max-width: 100%;
|
||||
margin: 0 auto -7px;
|
||||
object-fit: contain;
|
||||
|
||||
@@ -33,6 +33,49 @@ section {
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.hero-trust-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 22px;
|
||||
padding: 10px 16px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border: 1px solid rgba(255, 255, 255, 0.14);
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
text-decoration: none;
|
||||
transition:
|
||||
background 0.2s ease,
|
||||
transform 0.16s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
.hero-trust-logo {
|
||||
width: 18px;
|
||||
height: 19px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.hero-trust-stars {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
color: var(--yellow);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.hero-trust-chip:hover {
|
||||
background: rgba(255, 255, 255, 0.16);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
}
|
||||
|
||||
.hero-trust-chip:active {
|
||||
transform: translateY(1px) scale(0.99);
|
||||
}
|
||||
|
||||
.hero-buttons {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
@@ -585,6 +628,12 @@ footer {
|
||||
transition: background 0.2s, opacity 0.2s;
|
||||
}
|
||||
|
||||
.footer-google-logo {
|
||||
width: 16px;
|
||||
height: 17px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.footer-reviews:hover {
|
||||
background: rgba(255, 255, 255, 0.13);
|
||||
opacity: 1;
|
||||
|
||||
@@ -232,5 +232,6 @@ export interface SiteSharedContent {
|
||||
services: IconCard[];
|
||||
testimonials: TestimonialContent[];
|
||||
booking: BookingContent;
|
||||
info: InfoContent;
|
||||
footer: FooterContent;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
import HeroSection from '$lib/components/HeroSection.svelte';
|
||||
import InfoSection from '$lib/components/InfoSection.svelte';
|
||||
import InstagramSection from '$lib/components/InstagramSection.svelte';
|
||||
import IntroStrip from '$lib/components/IntroStrip.svelte';
|
||||
import BookingSection from '$lib/components/BookingSection.svelte';
|
||||
import PromiseSection from '$lib/components/PromiseSection.svelte';
|
||||
import ServicesSection from '$lib/components/ServicesSection.svelte';
|
||||
@@ -137,12 +136,11 @@
|
||||
/>
|
||||
|
||||
<Header navigation={data.content.navigation} />
|
||||
<HeroSection hero={data.content.hero} />
|
||||
<IntroStrip intro={data.content.intro} />
|
||||
<HeroSection hero={data.content.hero} reviewCta={data.content.intro.reviewCta} />
|
||||
<PromiseSection promise={data.content.promise} />
|
||||
<ServicesSection services={data.content.services} />
|
||||
<ValuesSection values={data.content.values} />
|
||||
<TestimonialsSection testimonials={data.content.testimonials} />
|
||||
<ValuesSection values={data.content.values} />
|
||||
<BookingSection booking={data.content.booking} />
|
||||
<InfoSection info={data.content.info} />
|
||||
<InstagramSection instagram={data.content.instagram} />
|
||||
|
||||
@@ -218,7 +218,11 @@
|
||||
{:else if data.slug === 'privacy-policy'}
|
||||
<LegalPage pageContent={privacyPolicyContent} />
|
||||
{:else if data.slug === 'contact-us'}
|
||||
<BookingPage booking={data.content.booking} allowGeneralEnquiry={data.generalEnquiryEnabled} />
|
||||
<BookingPage
|
||||
booking={data.content.booking}
|
||||
info={data.content.info}
|
||||
allowGeneralEnquiry={data.generalEnquiryEnabled}
|
||||
/>
|
||||
{:else}
|
||||
<main class="static-page">
|
||||
<section class="static-page-hero">
|
||||
|
||||
@@ -60,4 +60,13 @@ describe('static slug route page', () => {
|
||||
|
||||
expect(screen.queryByLabelText(/General enquiry/i)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders the shared FAQ section on the contact page', () => {
|
||||
render(SlugPage, {
|
||||
data: createStaticRouteData('contact-us')
|
||||
});
|
||||
|
||||
expect(screen.getByText('FAQs')).toBeInTheDocument();
|
||||
expect(screen.getByText('Can any dog use your service?')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,6 +6,7 @@ export const sharedPageContent = {
|
||||
services: homepageContent.services,
|
||||
testimonials: homepageContent.testimonials,
|
||||
booking: homepageContent.booking,
|
||||
info: homepageContent.info,
|
||||
footer: homepageContent.footer
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user